diff --git a/.eslintrc.json b/.eslintrc.json index 55db8c49169..65affdc01d5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,18 @@ { + "plugins": [ + "@typescript-eslint", + "import", + "eslint-plugin-tsdoc", + "prettier" + ], "extends": [ "react-app", "eslint:recommended", "plugin:react/recommended", + "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" ], - "parser": "babel-eslint", + "parser": "@typescript-eslint/parser", "env": { "jest": true }, diff --git a/.vscode/launch.json b/.vscode/launch.json index 1689524c2fa..f085796681f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,5 +11,18 @@ "url": "http://localhost:3000", "webRoot": "${workspaceFolder}" } + // { + // "name": "Debug Jest Tests", + // "type": "node", + // "request": "launch", + // "runtimeArgs": [ + // "--inspect-brk", + // "${workspaceRoot}/node_modules/.bin/jest", + // "--runInBand" + // ], + // "console": "integratedTerminal", + // "internalConsoleOptions": "neverOpen", + // "port": 9229 + // } ] } diff --git a/.webpack/rules/transpileJavaScript.js b/.webpack/rules/transpileJavaScript.js index 59e9c2b816f..d576e58393e 100644 --- a/.webpack/rules/transpileJavaScript.js +++ b/.webpack/rules/transpileJavaScript.js @@ -4,7 +4,6 @@ function transpileJavaScript(mode) { const exclude = mode === 'production' ? excludeNodeModulesExcept([ - 'vtk.js', // 'dicomweb-client', // https://github.com/react-dnd/react-dnd/blob/master/babel.config.js 'react-dnd', @@ -21,7 +20,8 @@ function transpileJavaScript(mode) { : excludeNodeModulesExcept([]); return { - test: /\.(mjs|js|jsx)?$/, + // Include mjs, ts, tsx, js, and jsx files. + test: /\.(mjs|ts|js)x?$/, // These are packages that are not transpiled to our lowest supported // JS version (currently ES5). Most of these leverage ES6+ features, // that we need to transpile to a different syntax. diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index d5cd08f49a8..5ef24dbfd7f 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -12,6 +12,8 @@ const CopyPlugin = require('copy-webpack-plugin'); // ~~ PackageJSON const PACKAGE = require('../platform/viewer/package.json'); +// const vtkRules = require('vtk.js/Utilities/config/dependency.js').webpack.core +// .rules; // ~~ RULES const loadShadersRule = require('./rules/loadShaders.js'); const loadWebWorkersRule = require('./rules/loadWebWorkers.js'); @@ -66,11 +68,20 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => { children: false, warnings: true, }, + devServer: { + open: true, + port: 3000, + historyApiFallback: true, + headers: { + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin', + }, + }, module: { rules: [ transpileJavaScriptRule(mode), loadWebWorkersRule, - loadShadersRule, + // loadShadersRule, { test: /\.m?js/, resolve: { @@ -78,7 +89,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => { }, }, cssToJavaScript, - ], + ], //.concat(vtkRules), }, resolve: { mainFields: ['module', 'browser', 'main'], @@ -106,7 +117,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => { SRC_DIR, ], // Attempt to resolve these extensions in order. - extensions: ['.js', '.jsx', '.json', '*'], + extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '*'], // symlinked resources are resolved to their real path, not their symlinked location symlinks: true, fallback: { fs: false, path: false, zlib: false }, diff --git a/README.md b/README.md index bd3903e0827..ca0ade11228 100644 --- a/README.md +++ b/README.md @@ -190,13 +190,14 @@ you'll see the following: ├── extensions # │ ├── _example # Skeleton of example extension │ ├── default # -│ ├── cornerstone # 2D images w/ Cornerstone.js -│ ├── dicom-sr # +│ ├── cornerstone # image rendering and tools w/ Cornerstone +│ ├── cornerstone- dicom-sr # │ └── measurement-tracking # │ ├── modes # │ ├── _example # Skeleton of example mode -│ └── longitudinal # +│ ├── basic-dev-mode # Basic development mode +│ └── longitudinal # Longitudinal mode (measurement tracking) │ ├── platform # │ ├── core # Business Logic diff --git a/babel.config.js b/babel.config.js index 0adf64bed7c..3650eee2daf 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,10 +1,16 @@ -const aliases = require('./aliases.config'); -const path = require('path'); - // https://babeljs.io/docs/en/options#babelrcroots module.exports = { babelrcRoots: ['./platform/*', './extensions/*', './modes/*'], - plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'], + presets: [ + '@babel/preset-env', + '@babel/preset-react', + '@babel/preset-typescript', + ], + plugins: [ + 'inline-react-svg', + '@babel/plugin-proposal-class-properties', + '@babel/plugin-transform-typescript', + ], env: { test: { presets: [ @@ -17,12 +23,14 @@ module.exports = { }, ], '@babel/preset-react', + '@babel/preset-typescript', ], plugins: [ '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-syntax-dynamic-import', '@babel/plugin-transform-regenerator', '@babel/plugin-transform-runtime', + '@babel/plugin-transform-typescript', ], }, production: { @@ -30,6 +38,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -38,6 +47,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/dicom-sr/.webpack/webpack.dev.js b/extensions/cornerstone-dicom-sr/.webpack/webpack.dev.js similarity index 100% rename from extensions/dicom-sr/.webpack/webpack.dev.js rename to extensions/cornerstone-dicom-sr/.webpack/webpack.dev.js diff --git a/extensions/dicom-sr/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js similarity index 97% rename from extensions/dicom-sr/.webpack/webpack.prod.js rename to extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js index 08b25fc133d..e1444944f1e 100644 --- a/extensions/dicom-sr/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js @@ -14,7 +14,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/extensions/dicom-sr/LICENSE b/extensions/cornerstone-dicom-sr/LICENSE similarity index 100% rename from extensions/dicom-sr/LICENSE rename to extensions/cornerstone-dicom-sr/LICENSE diff --git a/extensions/dicom-sr/README.md b/extensions/cornerstone-dicom-sr/README.md similarity index 100% rename from extensions/dicom-sr/README.md rename to extensions/cornerstone-dicom-sr/README.md diff --git a/extensions/dicom-sr/babel.config.js b/extensions/cornerstone-dicom-sr/babel.config.js similarity index 100% rename from extensions/dicom-sr/babel.config.js rename to extensions/cornerstone-dicom-sr/babel.config.js diff --git a/extensions/dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json similarity index 79% rename from extensions/dicom-sr/package.json rename to extensions/cornerstone-dicom-sr/package.json index 17914702be0..c5d33aae618 100644 --- a/extensions/dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -1,12 +1,12 @@ { - "name": "@ohif/extension-dicom-sr", + "name": "@ohif/extension-cornerstone-dicom-sr", "version": "3.0.0", "description": "OHIF extension for an SR Cornerstone Viewport", "author": "OHIF", "license": "MIT", "repository": "OHIF/Viewers", "main": "dist/index.umd.js", - "module": "src/index.js", + "module": "src/index.tsx", "engines": { "node": ">=14", "npm": ">=6", @@ -34,21 +34,18 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "cornerstone-core": "^2.6.0", - "cornerstone-math": "^0.1.9", - "cornerstone-tools": "6.0.2", - "cornerstone-wado-image-loader": "4.0.4", - "dcmjs": "0.16.1", + "dcmjs": "^0.24.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", "react": "^17.0.2", - "react-cornerstone-viewport": "4.1.2", "@ohif/extension-cornerstone": "^3.0.0", "@ohif/extension-measurement-tracking": "^3.0.0" }, "dependencies": { "@babel/runtime": "7.16.3", - "classnames": "^2.2.6" + "classnames": "^2.2.6", + "@cornerstonejs/core": "^0.13.11", + "@cornerstonejs/tools": "^0.20.15" } } diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.js b/extensions/cornerstone-dicom-sr/src/commandsModule.js new file mode 100644 index 00000000000..253583d151c --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/commandsModule.js @@ -0,0 +1,143 @@ +import { metaData, utilities } from '@cornerstonejs/core'; + +import OHIF from '@ohif/core'; +import dcmjs from 'dcmjs'; + +import getFilteredCornerstoneToolState from './utils/getFilteredCornerstoneToolState'; + +const { MeasurementReport } = dcmjs.adapters.Cornerstone3D; +const { log } = OHIF; + +/** + * + * @param measurementData An array of measurements from the measurements service + * that you wish to serialize. + * @param additionalFindingTypes toolTypes that should be stored with labels as Findings + * @param options Naturalized DICOM JSON headers to merge into the displaySet. + * + */ +const _generateReport = ( + measurementData, + additionalFindingTypes, + options = {} +) => { + const filteredToolState = getFilteredCornerstoneToolState( + measurementData, + additionalFindingTypes + ); + + const report = MeasurementReport.generateReport( + filteredToolState, + metaData, + utilities.worldToImageCoords + ); + + const { dataset } = report; + + // Add in top level series options + Object.assign(dataset, options); + + return dataset; +}; + +const commandsModule = ({}) => { + const actions = { + /** + * + * @param measurementData An array of measurements from the measurements service + * @param additionalFindingTypes toolTypes that should be stored with labels as Findings + * @param options Naturalized DICOM JSON headers to merge into the displaySet. + * as opposed to Finding Sites. + * that you wish to serialize. + */ + downloadReport: ({ + measurementData, + additionalFindingTypes, + options = {}, + }) => { + const srDataset = actions.generateReport( + measurementData, + additionalFindingTypes, + options + ); + const reportBlob = dcmjs.data.datasetToBlob(srDataset); + + //Create a URL for the binary. + var objectUrl = URL.createObjectURL(reportBlob); + window.location.assign(objectUrl); + }, + + /** + * + * @param measurementData An array of measurements from the measurements service + * that you wish to serialize. + * @param dataSource The dataSource that you wish to use to persist the data. + * @param additionalFindingTypes toolTypes that should be stored with labels as Findings + * @param options Naturalized DICOM JSON headers to merge into the displaySet. + * @return The naturalized report + */ + storeMeasurements: async ({ + measurementData, + dataSource, + additionalFindingTypes, + options = {}, + }) => { + // TODO -> Eventually use the measurements directly and not the dcmjs adapter, + // But it is good enough for now whilst we only have cornerstone as a datasource. + log.info('[DICOMSR] storeMeasurements'); + + if (!dataSource || !dataSource.store || !dataSource.store.dicom) { + log.error( + '[DICOMSR] datasource has no dataSource.store.dicom endpoint!' + ); + return Promise.reject({}); + } + + try { + const naturalizedReport = _generateReport( + measurementData, + additionalFindingTypes, + options + ); + const { StudyInstanceUID } = naturalizedReport; + + await dataSource.store.dicom(naturalizedReport); + + if (StudyInstanceUID) { + dataSource.deleteStudyMetadataPromise(StudyInstanceUID); + } + + return naturalizedReport; + } catch (error) { + console.warn(error); + log.error( + `[DICOMSR] Error while saving the measurements: ${error.message}` + ); + throw new Error( + error.message || 'Error while saving the measurements.' + ); + } + }, + }; + + const definitions = { + downloadReport: { + commandFn: actions.downloadReport, + storeContexts: [], + options: {}, + }, + storeMeasurements: { + commandFn: actions.storeMeasurements, + storeContexts: [], + options: {}, + }, + }; + + return { + actions, + definitions, + defaultContext: 'CORNERSTONE_STRUCTURED_REPORT', + }; +}; + +export default commandsModule; diff --git a/extensions/dicom-sr/src/constants/scoordTypes.js b/extensions/cornerstone-dicom-sr/src/constants/scoordTypes.js similarity index 100% rename from extensions/dicom-sr/src/constants/scoordTypes.js rename to extensions/cornerstone-dicom-sr/src/constants/scoordTypes.js diff --git a/extensions/dicom-sr/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js similarity index 94% rename from extensions/dicom-sr/src/getSopClassHandlerModule.js rename to extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js index cc877c409e4..d41d6d14710 100644 --- a/extensions/dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.js @@ -2,9 +2,11 @@ import { SOPClassHandlerName, SOPClassHandlerId } from './id'; import { utils, classes } from '@ohif/core'; import addMeasurement from './utils/addMeasurement'; import isRehydratable from './utils/isRehydratable'; +import { adapters } from 'dcmjs'; -const { ImageSet } = classes; +const { CodeScheme: Cornerstone3DCodeScheme } = adapters.Cornerstone3D; +const { ImageSet } = classes; // TODO -> // Add SR thumbnail // Make viewport @@ -16,6 +18,9 @@ const sopClassUids = [ '1.2.840.10008.5.1.4.1.1.88.33', //COMPREHENSIVE_SR: ]; +const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; +const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; + const CodeNameCodeSequenceValues = { ImagingMeasurementReport: '126000', ImageLibrary: '111028', @@ -26,12 +31,15 @@ const CodeNameCodeSequenceValues = { TrackingIdentifier: '112039', Finding: '121071', FindingSite: 'G-C0E3', // SRT - CornerstoneFreeText: 'CORNERSTONEFREETEXT', // CST4 + CornerstoneFreeText: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT, // }; const CodingSchemeDesignators = { SRT: 'SRT', - cornerstoneTools4: 'CST4', + CornerstoneCodeSchemes: [ + Cornerstone3DCodeScheme.CodingSchemeDesignator, + 'CST4', + ], }; const RELATIONSHIP_TYPE = { @@ -117,8 +125,8 @@ function _load(displaySet, servicesManager, extensionManager) { displaySet.measurements = _getMeasurements(ContentSequence); const mappings = MeasurementService.getSourceMappings( - 'CornerstoneTools', - '4' + CORNERSTONE_3D_TOOLS_SOURCE_NAME, + CORNERSTONE_3D_TOOLS_SOURCE_VERSION ); displaySet.isHydrated = false; @@ -217,7 +225,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( for (let j = unloadedMeasurements.length - 1; j >= 0; j--) { const measurement = unloadedMeasurements[j]; - if (_measurmentReferencesSOPInstanceUID(measurement, SOPInstanceUID)) { + if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID)) { addMeasurement( measurement, imageId, @@ -231,7 +239,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( } } -function _measurmentReferencesSOPInstanceUID(measurement, SOPInstanceUID) { +function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID) { const { coords } = measurement; for (let j = 0; j < coords.length; j++) { @@ -458,8 +466,9 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { if ( Finding && - Finding.ConceptCodeSequence.CodingSchemeDesignator === - CodingSchemeDesignators.cornerstoneTools4 && + CodingSchemeDesignators.CornerstoneCodeSchemes.includes( + Finding.ConceptCodeSequence.CodingSchemeDesignator + ) && Finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ) { @@ -473,8 +482,9 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { if (FindingSites.length) { const cornerstoneFreeTextFindingSite = FindingSites.find( FindingSite => - FindingSite.ConceptCodeSequence.CodingSchemeDesignator === - CodingSchemeDesignators.cornerstoneTools4 && + CodingSchemeDesignators.CornerstoneCodeSchemes.includes( + FindingSite.ConceptCodeSequence.CodingSchemeDesignator + ) && FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ); diff --git a/extensions/dicom-sr/src/id.js b/extensions/cornerstone-dicom-sr/src/id.js similarity index 100% rename from extensions/dicom-sr/src/id.js rename to extensions/cornerstone-dicom-sr/src/id.js diff --git a/extensions/dicom-sr/src/index.js b/extensions/cornerstone-dicom-sr/src/index.tsx similarity index 56% rename from extensions/dicom-sr/src/index.js rename to extensions/cornerstone-dicom-sr/src/index.tsx index 61fa0a6a19a..e53927d6d03 100644 --- a/extensions/dicom-sr/src/index.js +++ b/extensions/cornerstone-dicom-sr/src/index.tsx @@ -1,8 +1,10 @@ import React from 'react'; import getSopClassHandlerModule from './getSopClassHandlerModule'; import onModeEnter from './onModeEnter'; +import commandsModule from './commandsModule'; import init from './init'; import { id } from './id.js'; +import toolNames from './tools/toolNames'; const Component = React.lazy(() => { return import( @@ -21,11 +23,12 @@ const OHIFCornerstoneSRViewport = props => { /** * */ -export default { +const dicomSRExtension = { /** * Only required property. Should be a unique value across all extensions. */ id, + onModeEnter, preRegistration({ servicesManager, configuration = {} }) { init({ servicesManager, configuration }); @@ -50,50 +53,24 @@ export default { return [{ name: 'dicom-sr', component: ExtendedOHIFCornerstoneSRViewport }]; }, - getCommandsModule({ servicesManager }) { - return { - definitions: { - setToolActive: { - commandFn: ({ toolName, element }) => { - if (!toolName) { - console.warn('No toolname provided to setToolActive command'); - } - - // Set same tool or alt tool - const toolAlias = _getToolAlias(toolName); - - cornerstoneTools.setToolActiveForElement(element, toolAlias, { - mouseButtonMask: 1, - }); - }, - storeContexts: [], - options: {}, + getCommandsModule({ servicesManager, commandsManager, extensionManager }) { + return commandsModule({ + servicesManager, + commandsManager, + extensionManager, + }); + }, + getSopClassHandlerModule, + getUtilityModule({ servicesManager }) { + return [ + { + name: 'tools', + exports: { + toolNames, }, }, - defaultContext: 'ACTIVE_VIEWPORT::STRUCTURED_REPORT', - }; + ]; }, - getSopClassHandlerModule, - onModeEnter, }; -function _getToolAlias(toolName) { - let toolAlias = toolName; - - switch (toolName) { - case 'Length': - toolAlias = 'SRLength'; - break; - case 'Bidirectional': - toolAlias = 'SRBidirectional'; - break; - case 'ArrowAnnotate': - toolAlias = 'SRArrowAnnotate'; - break; - case 'EllipticalRoi': - toolAlias = 'SREllipticalRoi'; - break; - } - - return toolAlias; -} +export default dicomSRExtension; diff --git a/extensions/cornerstone-dicom-sr/src/init.js b/extensions/cornerstone-dicom-sr/src/init.js new file mode 100644 index 00000000000..92a907ff86c --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/init.js @@ -0,0 +1,29 @@ +import { addTool, annotation } from '@cornerstonejs/tools'; +import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool'; +import SRLengthTool from './tools/tools/SRLength'; +import SRBidirectionalTool from './tools/tools/SRBidirectional'; +import SREllipticalROITool from './tools/tools/SREllipticalROI'; +import SRArrowAnnotateTool from './tools/tools/SRArrowAnnotate'; + +/** + * @param {object} configuration + */ +export default function init({ configuration = {} }) { + addTool(DICOMSRDisplayTool); + addTool(SRLengthTool); + addTool(SRBidirectionalTool); + addTool(SREllipticalROITool); + addTool(SRArrowAnnotateTool); + + // Modify annotation tools to use dashed lines on SR + const dashedLine = { + lineDash: '4,4', + }; + annotation.config.style.setToolGroupToolStyles('SRToolGroup', { + [SRLengthTool.toolName]: dashedLine, + [SRBidirectionalTool.toolName]: dashedLine, + [SREllipticalROITool.toolName]: dashedLine, + [SRArrowAnnotateTool.toolName]: dashedLine, + global: {}, + }); +} diff --git a/extensions/dicom-sr/src/onModeEnter.js b/extensions/cornerstone-dicom-sr/src/onModeEnter.js similarity index 100% rename from extensions/dicom-sr/src/onModeEnter.js rename to extensions/cornerstone-dicom-sr/src/onModeEnter.js diff --git a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts new file mode 100644 index 00000000000..809abea6e4a --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts @@ -0,0 +1,388 @@ +import { Types, metaData, utilities as csUtils } from '@cornerstonejs/core'; +import { + AnnotationTool, + annotation, + drawing, + utilities, + Types as cs3DToolsTypes, +} from '@cornerstonejs/tools'; +import { getTrackingUniqueIdentifiersForElement } from './modules/dicomSRModule'; +import SCOORD_TYPES from '../constants/scoordTypes'; + +export default class DICOMSRDisplayTool extends AnnotationTool { + static toolName = 'DICOMSRDisplay'; + + constructor( + toolProps = {}, + defaultToolProps = { + configuration: {}, + } + ) { + super(toolProps, defaultToolProps); + } + + _getTextBoxLinesFromLabels(labels) { + // TODO -> max 3 for now (label + shortAxis + longAxis), need a generic solution for this! + + const labelLength = Math.min(labels.length, 3); + const lines = []; + + for (let i = 0; i < labelLength; i++) { + const labelEntry = labels[i]; + lines.push(`${_labelToShorthand(labelEntry.label)}${labelEntry.value}`); + } + + return lines; + } + + // This tool should not inherit from AnnotationTool and we should not need + // to add the following lines. + isPointNearTool = () => null; + getHandleNearImagePoint = () => null; + + renderAnnotation = ( + enabledElement: Types.IEnabledElement, + svgDrawingHelper: any + ): void => { + const { viewport } = enabledElement; + const { element } = viewport; + + let annotations = annotation.state.getAnnotations( + element, + this.getToolName() + ); + + // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender + if (!annotations?.length) { + return; + } + + annotations = this.filterInteractableAnnotationsForElement( + element, + annotations + ); + + if (!annotations?.length) { + return; + } + + const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement( + element + ); + + const { + activeIndex, + trackingUniqueIdentifiers, + } = trackingUniqueIdentifiersForElement; + + const activeTrackingUniqueIdentifier = + trackingUniqueIdentifiers[activeIndex]; + + // Filter toolData to only render the data for the active SR. + const filteredAnnotations = annotations.filter(annotation => + trackingUniqueIdentifiers.includes( + annotation.data?.cachedStats?.TrackingUniqueIdentifier + ) + ); + + if (!viewport._actors?.size) { + return; + } + + const styleSpecifier: cs3DToolsTypes.AnnotationStyle.StyleSpecifier = { + toolGroupId: this.toolGroupId, + toolName: this.getToolName(), + viewportId: enabledElement.viewport.id, + }; + + for (let i = 0; i < filteredAnnotations.length; i++) { + const annotation = filteredAnnotations[i]; + const annotationUID = annotation.annotationUID; + const { renderableData } = annotation.data.cachedStats; + const { label, cachedStats } = annotation.data; + const { referencedImageId } = annotation.metadata; + + styleSpecifier.annotationUID = annotationUID; + + const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation); + const lineDash = this.getStyle('lineDash', styleSpecifier, annotation); + const color = + cachedStats.TrackingUniqueIdentifier === activeTrackingUniqueIdentifier + ? 'rgb(0, 255, 0)' + : this.getStyle('color', styleSpecifier, annotation); + + const options = { + color, + lineDash, + lineWidth, + }; + + Object.keys(renderableData).forEach(GraphicType => { + const renderableDataForGraphicType = renderableData[GraphicType]; + + let renderMethod; + + switch (GraphicType) { + case SCOORD_TYPES.POINT: + renderMethod = this.renderPoint; + break; + case SCOORD_TYPES.MULTIPOINT: + renderMethod = this.renderMultipoint; + break; + case SCOORD_TYPES.POLYLINE: + renderMethod = this.renderPolyLine; + break; + case SCOORD_TYPES.CIRCLE: + renderMethod = this.renderEllipse; + break; + case SCOORD_TYPES.ELLIPSE: + renderMethod = this.renderEllipse; + break; + default: + throw new Error(`Unsupported GraphicType: ${GraphicType}`); + } + + const canvasCoordinates = renderMethod( + svgDrawingHelper, + viewport, + renderableDataForGraphicType, + annotationUID, + referencedImageId, + options + ); + + if (!canvasCoordinates) { + return; + } + + const textLines = this._getTextBoxLinesFromLabels(label); + + let canvasCornersToUseForTextBox = canvasCoordinates; + + if (GraphicType === SCOORD_TYPES.ELLIPSE) { + canvasCornersToUseForTextBox = utilities.math.ellipse.getCanvasEllipseCorners( + canvasCoordinates + ); + } + + const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas( + canvasCornersToUseForTextBox + ); + + annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld( + canvasTextBoxCoords + ); + + const textBoxPosition = viewport.worldToCanvas( + annotation.data.handles.textBox.worldPosition + ); + + const textBoxUID = '1'; + const textBoxOptions = this.getLinkedTextBoxStyle( + styleSpecifier, + annotation + ); + + const boundingBox = drawing.drawLinkedTextBox( + svgDrawingHelper, + annotationUID, + textBoxUID, + textLines, + textBoxPosition, + canvasCoordinates, + {}, + { + ...textBoxOptions, + color, + } + ); + + const { x: left, y: top, width, height } = boundingBox; + + annotation.data.handles.textBox.worldBoundingBox = { + topLeft: viewport.canvasToWorld([left, top]), + topRight: viewport.canvasToWorld([left + width, top]), + bottomLeft: viewport.canvasToWorld([left, top + height]), + bottomRight: viewport.canvasToWorld([left + width, top + height]), + }; + }); + } + }; + + renderPolyLine( + svgDrawingHelper, + viewport, + renderableData, + annotationUID, + referencedImageId, + options + ) { + // Todo: this needs to use the drawPolyLine from cs3D since it is implemented + // now, before it was implemented with a loop over drawLine which is hacky + + let canvasCoordinates; + renderableData.map((data, index) => { + canvasCoordinates = data.map(p => viewport.worldToCanvas(p)); + + if (canvasCoordinates.length === 2) { + const lineUID = `${index}`; + drawing.drawLine( + svgDrawingHelper, + annotationUID, + lineUID, + canvasCoordinates[0], + canvasCoordinates[1], + { + color: options.color, + width: options.lineWidth, + } + ); + } else { + throw new Error('Drawing polyline for SR not yet implemented'); + } + }); + + return canvasCoordinates; // used for drawing textBox + } + + renderMultipoint( + svgDrawingHelper, + viewport, + renderableData, + annotationUID, + referencedImageId, + options + ) { + let canvasCoordinates; + renderableData.map((data, index) => { + canvasCoordinates = data.map(p => viewport.worldToCanvas(p)); + const handleGroupUID = '0'; + drawing.drawHandles( + svgDrawingHelper, + annotationUID, + handleGroupUID, + canvasCoordinates, + { + color: options.color, + } + ); + }); + } + + renderPoint( + svgDrawingHelper, + viewport, + renderableData, + annotationUID, + referencedImageId, + options + ) { + const canvasCoordinates = []; + renderableData.map((data, index) => { + const point = data[0]; + // This gives us one point for arrow + canvasCoordinates.push(viewport.worldToCanvas(point)); + + // We get the other point for the arrow by using the image size + const imagePixelModule = metaData.get( + 'imagePixelModule', + referencedImageId + ); + + let xOffset = 10; + let yOffset = 10; + + if (imagePixelModule) { + const { columns, rows } = imagePixelModule; + xOffset = columns / 10; + yOffset = rows / 10; + } + + const imagePoint = csUtils.worldToImageCoords(referencedImageId, point); + const arrowEnd = csUtils.imageToWorldCoords(referencedImageId, [ + imagePoint[0] + xOffset, + imagePoint[1] + yOffset, + ]); + + canvasCoordinates.push(viewport.worldToCanvas(arrowEnd)); + + const arrowUID = `${index}`; + + // Todo: handle drawing probe as probe, currently we are drawing it as an arrow + drawing.drawArrow( + svgDrawingHelper, + annotationUID, + arrowUID, + canvasCoordinates[1], + canvasCoordinates[0], + { + color: options.color, + width: options.lineWidth, + } + ); + }); + + return canvasCoordinates; // used for drawing textBox + } + + renderEllipse( + svgDrawingHelper, + viewport, + renderableData, + annotationUID, + referencedImageId, + options + ) { + let canvasCoordinates; + renderableData.map((data, index) => { + if (data.length === 0) { + // since oblique ellipse is not supported for hydration right now + // we just return + return; + } + + const ellipsePointsWorld = data; + + canvasCoordinates = ellipsePointsWorld.map(p => + viewport.worldToCanvas(p) + ); + + const canvasCorners = >( + utilities.math.ellipse.getCanvasEllipseCorners(canvasCoordinates) + ); + + const lineUID = `${index}`; + drawing.drawEllipse( + svgDrawingHelper, + annotationUID, + lineUID, + canvasCorners[0], + canvasCorners[1], + { + color: options.color, + width: options.lineWidth, + } + ); + }); + + return canvasCoordinates; + } +} + +const SHORT_HAND_MAP = { + 'Short Axis': 'W ', + 'Long Axis': 'L ', + AREA: 'Area ', + Length: '', + CORNERSTONEFREETEXT: '', +}; + +function _labelToShorthand(label) { + const shortHand = SHORT_HAND_MAP[label]; + + if (shortHand !== undefined) { + return shortHand; + } + + return label; +} diff --git a/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js new file mode 100644 index 00000000000..f359e3d2737 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js @@ -0,0 +1,64 @@ +import { getEnabledElement } from '@cornerstonejs/core'; + +const state = { + TrackingUniqueIdentifier: null, + trackingIdentifiersByViewportId: {}, +}; + +/** + * This file is being used to store the per-viewport state of the SR tools, + * Since, all the toolStates are added to the cornerstoneTools, when displaying the SRTools, + * if there are two viewports rendering the same imageId, we don't want to show + * the same SR annotation twice on irrelevant viewport, hence, we are storing the state + * of the SR tools in state here, so that we can filter them later. + */ + +function setTrackingUniqueIdentifiersForElement( + element, + trackingUniqueIdentifiers, + activeIndex = 0 +) { + const enabledElement = getEnabledElement(element); + const { viewport } = enabledElement; + + state.trackingIdentifiersByViewportId[viewport.id] = { + trackingUniqueIdentifiers, + activeIndex, + }; +} + +function setActiveTrackingUniqueIdentifierForElement( + element, + TrackingUniqueIdentifier +) { + const enabledElement = getEnabledElement(element); + const { viewport } = enabledElement; + + const trackingIdentifiersForElement = + state.trackingIdentifiersByViewportId[viewport.id]; + + if (trackingIdentifiersForElement) { + const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex( + tuid => tuid === TrackingUniqueIdentifier + ); + + trackingIdentifiersForElement.activeIndex = activeIndex; + } +} + +function getTrackingUniqueIdentifiersForElement(element) { + const enabledElement = getEnabledElement(element); + const { viewport } = enabledElement; + + if (state.trackingIdentifiersByViewportId[viewport.id]) { + return state.trackingIdentifiersByViewportId[viewport.id]; + } + + return { trackingUniqueIdentifiers: [] }; +} + +export { + setTrackingUniqueIdentifiersForElement, + setActiveTrackingUniqueIdentifierForElement, + getTrackingUniqueIdentifiersForElement, +}; diff --git a/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts new file mode 100644 index 00000000000..5e714f831e2 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/tools/toolNames.ts @@ -0,0 +1,15 @@ +import DICOMSRDisplayTool from './DICOMSRDisplayTool'; +import SRLengthTool from './tools/SRLength'; +import SRBidirectional from './tools/SRBidirectional'; +import SREllipticalROI from './tools/SREllipticalROI'; +import SRArrowAnnotate from './tools/SRArrowAnnotate'; + +const toolNames = { + DICOMSRDisplay: DICOMSRDisplayTool.toolName, + SRLength: SRLengthTool.toolName, + SRBidirectional: SRBidirectional.toolName, + SREllipticalROI: SREllipticalROI.toolName, + SRArrowAnnotate: SRArrowAnnotate.toolName, +}; + +export default toolNames; diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SRArrowAnnotate.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SRArrowAnnotate.ts new file mode 100644 index 00000000000..66b2a1e10d6 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/tools/tools/SRArrowAnnotate.ts @@ -0,0 +1,16 @@ +import { ArrowAnnotateTool } from '@cornerstonejs/tools'; + +/** + * The reason we are extending ArrowAnnotateTool is to create a new tool for SR + * viewport which basically has a different name. This is done since Cornerstone + * has shifted from creating tool instances for each annotation, and we have ArrowAnnotate + * mappers at the MeasurementService, so if we didn't do this, we would be mapping + * the SR annotation to the MeasurementService (since there is a ArrowAnnotateTool mapper), + * but with extending and renaming it, there is not mapper for SRArrowAnnotateTool; hence + * no mapping; hence no new measurement, just temporary ones for the SR viewport. + */ +class SRArrowAnnotateTool extends ArrowAnnotateTool { + static toolName = 'SRArrowAnnotate'; +} + +export default SRArrowAnnotateTool; diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SRBidirectional.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SRBidirectional.ts new file mode 100644 index 00000000000..458f4b1866f --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/tools/tools/SRBidirectional.ts @@ -0,0 +1,16 @@ +import { BidirectionalTool } from '@cornerstonejs/tools'; + +/** + * The reason we are extending BidirectionalTool is to create a new tool for SR + * viewport which basically has a different name. This is done since Cornerstone + * has shifted from creating tool instances for each annotation, and we have Bidirectional + * mappers at the MeasurementService, so if we didn't do this, we would be mapping + * the SR annotation to the MeasurementService (since there is a BidirectionalTool mapper), + * but with extending and renaming it, there is not mapper for SRBidirectionalTool; hence + * no mapping; hence no new measurement, just temporary ones for the SR viewport. + */ +class SRBidirectional extends BidirectionalTool { + static toolName = 'SRBidirectional'; +} + +export default SRBidirectional; diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SREllipticalROI.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SREllipticalROI.ts new file mode 100644 index 00000000000..08f0f700594 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/tools/tools/SREllipticalROI.ts @@ -0,0 +1,16 @@ +import { EllipticalROITool } from '@cornerstonejs/tools'; + +/** + * The reason we are extending EllipticalROITool is to create a new tool for SR + * viewport which basically has a different name. This is done since Cornerstone + * has shifted from creating tool instances for each annotation, and we have EllipticalROI + * mappers at the MeasurementService, so if we didn't do this, we would be mapping + * the SR annotation to the MeasurementService (since there is a EllipticalROITool mapper), + * but with extending and renaming it, there is not mapper for SREllipticalROITool; hence + * no mapping; hence no new measurement, just temporary ones for the SR viewport. + */ +class SREllipticalROI extends EllipticalROITool { + static toolName = 'SREllipticalROI'; +} + +export default SREllipticalROI; diff --git a/extensions/cornerstone-dicom-sr/src/tools/tools/SRLength.ts b/extensions/cornerstone-dicom-sr/src/tools/tools/SRLength.ts new file mode 100644 index 00000000000..62acba7c730 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/tools/tools/SRLength.ts @@ -0,0 +1,16 @@ +import { LengthTool } from '@cornerstonejs/tools'; + +/** + * The reason we are extending LengthTool is to create a new tool for SR + * viewport which basically has a different name. This is done since Cornerstone + * has shifted from creating tool instances for each annotation, and we have Length + * mappers at the MeasurementService, so if we didn't do this, we would be mapping + * the SR annotation to the MeasurementService (since there is a LengthTool mapper), + * but with extending and renaming it, there is not mapper for SRLengthTool; hence + * no mapping; hence no new measurement, just temporary ones for the SR viewport. + */ +class SRLengthTool extends LengthTool { + static toolName = 'SRLength'; +} + +export default SRLengthTool; diff --git a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts new file mode 100644 index 00000000000..9f1cedd399b --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts @@ -0,0 +1,244 @@ +import { vec3 } from 'gl-matrix'; +import { Types, annotation } from '@cornerstonejs/tools'; +import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core'; +import toolNames from '../tools/toolNames'; +import SCOORD_TYPES from '../constants/scoordTypes'; + +const EPSILON = 1e-4; + +const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; + +export default function addMeasurement( + measurement, + imageId, + displaySetInstanceUID +) { + // TODO -> Render rotated ellipse . + const toolName = toolNames.DICOMSRDisplay; + + const measurementData = { + TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier, + renderableData: {}, + labels: measurement.labels, + imageId, + }; + + measurement.coords.forEach(coord => { + const { GraphicType, GraphicData } = coord; + + if (measurementData.renderableData[GraphicType] === undefined) { + measurementData.renderableData[GraphicType] = []; + } + + measurementData.renderableData[GraphicType].push( + _getRenderableData( + GraphicType, + GraphicData, + imageId, + measurement.TrackingIdentifier + ) + ); + }); + + // Use the metadata provider to grab its imagePlaneModule metadata + const imagePlaneModule = metaData.get('imagePlaneModule', imageId); + + const annotationManager = annotation.state.getDefaultAnnotationManager(); + + const SRAnnotation: Types.Annotation = { + annotationUID: measurement.TrackingUniqueIdentifier, + metadata: { + FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID, + toolName: toolName, + referencedImageId: imageId, + }, + data: { + label: measurement.labels, + handles: { + textBox: {}, + }, + cachedStats: { + TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier, + renderableData: measurementData.renderableData, + }, + }, + }; + + annotationManager.addAnnotation(SRAnnotation); + + measurement.loaded = true; + measurement.imageId = imageId; + measurement.displaySetInstanceUID = displaySetInstanceUID; + + // Remove the unneeded coord now its processed, but keep the SOPInstanceUID. + // NOTE: We assume that each SCOORD in the MeasurementGroup maps onto one frame, + // It'd be super weird if it didn't anyway as a SCOORD. + measurement.ReferencedSOPInstanceUID = + measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID; + delete measurement.coords; +} + +function _getRenderableData( + GraphicType, + GraphicData, + imageId, + TrackingIdentifier +) { + const [cornerstoneTag, toolName] = TrackingIdentifier.split(':'); + + let renderableData: csTypes.Point3[]; + + switch (GraphicType) { + case SCOORD_TYPES.POINT: + case SCOORD_TYPES.MULTIPOINT: + case SCOORD_TYPES.POLYLINE: + renderableData = []; + + for (let i = 0; i < GraphicData.length; i += 2) { + const worldPos = utilities.imageToWorldCoords(imageId, [ + GraphicData[i], + GraphicData[i + 1], + ]); + + renderableData.push(worldPos); + } + + break; + case SCOORD_TYPES.CIRCLE: { + const pointsWorld = []; + for (let i = 0; i < GraphicData.length; i += 2) { + const worldPos = utilities.imageToWorldCoords(imageId, [ + GraphicData[i], + GraphicData[i + 1], + ]); + + pointsWorld.push(worldPos); + } + + // We do not have an explicit draw circle svg helper in Cornerstone3D at + // this time, but we can use the ellipse svg helper to draw a circle, so + // here we reshape the data for that purpose. + const center = pointsWorld[0]; + const onPerimeter = pointsWorld[1]; + + const radius = vec3.distance(center, onPerimeter); + + const imagePlaneModule = metaData.get('imagePlaneModule', imageId); + + if (!imagePlaneModule) { + throw new Error('No imagePlaneModule found'); + } + + const { + columnCosines, + rowCosines, + }: { + columnCosines: csTypes.Point3; + rowCosines: csTypes.Point3; + } = imagePlaneModule; + + // we need to get major/minor axis (which are both the same size major = minor) + + // first axisStart + const firstAxisStart = vec3.create(); + vec3.scaleAndAdd(firstAxisStart, center, columnCosines, radius); + + const firstAxisEnd = vec3.create(); + vec3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius); + + // second axisStart + const secondAxisStart = vec3.create(); + vec3.scaleAndAdd(secondAxisStart, center, rowCosines, radius); + + const secondAxisEnd = vec3.create(); + vec3.scaleAndAdd(secondAxisEnd, center, rowCosines, -radius); + + renderableData = [ + firstAxisStart as csTypes.Point3, + firstAxisEnd as csTypes.Point3, + secondAxisStart as csTypes.Point3, + secondAxisEnd as csTypes.Point3, + ]; + + break; + } + case SCOORD_TYPES.ELLIPSE: { + // GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY] + // But Cornerstone3D points are ordered as top, bottom, left, right for the + // ellipse so we need to identify if the majorAxis is horizontal or vertical + // and then choose the correct points to use for the ellipse. + + const pointsWorld: csTypes.Point3[] = []; + for (let i = 0; i < GraphicData.length; i += 2) { + const worldPos = utilities.imageToWorldCoords(imageId, [ + GraphicData[i], + GraphicData[i + 1], + ]); + + pointsWorld.push(worldPos); + } + + const majorAxisStart = vec3.fromValues(...pointsWorld[0]); + const majorAxisEnd = vec3.fromValues(...pointsWorld[1]); + const minorAxisStart = vec3.fromValues(...pointsWorld[2]); + const minorAxisEnd = vec3.fromValues(...pointsWorld[3]); + + const majorAxisVec = vec3.create(); + vec3.sub(majorAxisVec, majorAxisEnd, majorAxisStart); + + // normalize majorAxisVec to avoid scaling issues + vec3.normalize(majorAxisVec, majorAxisVec); + + const minorAxisVec = vec3.create(); + vec3.sub(minorAxisVec, minorAxisEnd, minorAxisStart); + vec3.normalize(minorAxisVec, minorAxisVec); + + const imagePlaneModule = metaData.get('imagePlaneModule', imageId); + + if (!imagePlaneModule) { + throw new Error('imageId does not have imagePlaneModule metadata'); + } + + const { + columnCosines, + }: { columnCosines: csTypes.Point3 } = imagePlaneModule; + + // find which axis is parallel to the columnCosines + const columnCosinesVec = vec3.fromValues(...columnCosines); + + const projectedMajorAxisOnColVec = Math.abs( + vec3.dot(columnCosinesVec, majorAxisVec) + ); + const projectedMinorAxisOnColVec = Math.abs( + vec3.dot(columnCosinesVec, minorAxisVec) + ); + + const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec); + const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec); + + renderableData = []; + if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) { + renderableData = [ + pointsWorld[0], + pointsWorld[1], + pointsWorld[2], + pointsWorld[3], + ]; + } else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) { + renderableData = [ + pointsWorld[2], + pointsWorld[3], + pointsWorld[0], + pointsWorld[1], + ]; + } else { + console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED'); + } + break; + } + default: + console.warn('Unsupported GraphicType:', GraphicType); + } + + return renderableData; +} diff --git a/platform/core/src/DICOMSR/utils/findInstanceMetadataBySopInstanceUid.js b/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js similarity index 100% rename from platform/core/src/DICOMSR/utils/findInstanceMetadataBySopInstanceUid.js rename to extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js diff --git a/platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js similarity index 100% rename from platform/core/src/DICOMSR/utils/findMostRecentStructuredReport.js rename to extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js diff --git a/platform/core/src/DICOMSR/utils/getAllDisplaySets.js b/extensions/cornerstone-dicom-sr/src/utils/getAllDisplaySets.js similarity index 100% rename from platform/core/src/DICOMSR/utils/getAllDisplaySets.js rename to extensions/cornerstone-dicom-sr/src/utils/getAllDisplaySets.js diff --git a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts new file mode 100644 index 00000000000..6dcecdd8810 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts @@ -0,0 +1,110 @@ +import OHIF from '@ohif/core'; +import { annotation } from '@cornerstonejs/tools'; +const { log } = OHIF; + +function getFilteredCornerstoneToolState( + measurementData, + additionalFindingTypes +) { + const filteredToolState = {}; + + function addToFilteredToolState(annotation, toolType) { + if (!annotation.metadata?.referencedImageId) { + log.warn( + `[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}` + ); + return; + } + + const imageId = annotation.metadata.referencedImageId; + + if (!filteredToolState[imageId]) { + filteredToolState[imageId] = {}; + } + + const imageIdSpecificToolState = filteredToolState[imageId]; + + if (!imageIdSpecificToolState[toolType]) { + imageIdSpecificToolState[toolType] = { + data: [], + }; + } + + const measurementDataI = measurementData.find( + md => md.uid === annotation.annotationUID + ); + const toolData = imageIdSpecificToolState[toolType].data; + + let finding; + const findingSites = []; + + // NOTE -> We use the CORNERSTONEJS coding schemeDesignator which we have + // defined in the dcmjs adapters + if (measurementDataI.label) { + if (additionalFindingTypes.includes(toolType)) { + finding = { + CodeValue: 'CORNERSTONEFREETEXT', + CodingSchemeDesignator: 'CORNERSTONEJS', + CodeMeaning: measurementDataI.label, + }; + } else { + findingSites.push({ + CodeValue: 'CORNERSTONEFREETEXT', + CodingSchemeDesignator: 'CORNERSTONEJS', + CodeMeaning: measurementDataI.label, + }); + } + } + + const measurement = Object.assign({}, annotation, { + finding, + findingSites, + }); + + toolData.push(measurement); + } + + const uidFilter = measurementData.map(md => md.uid); + const uids = uidFilter.slice(); + + const annotationManager = annotation.state.getDefaultAnnotationManager(); + const framesOfReference = annotationManager.getFramesOfReference(); + + for (let i = 0; i < framesOfReference.length; i++) { + const frameOfReference = framesOfReference[i]; + + const frameOfReferenceAnnotations = annotationManager.getFrameOfReferenceAnnotations( + frameOfReference + ); + + const toolTypes = Object.keys(frameOfReferenceAnnotations); + + for (let j = 0; j < toolTypes.length; j++) { + const toolType = toolTypes[j]; + + const annotations = frameOfReferenceAnnotations[toolType]; + + if (annotations) { + for (let k = 0; k < annotations.length; k++) { + const annotation = annotations[k]; + const uidIndex = uids.findIndex( + uid => uid === annotation.annotationUID + ); + + if (uidIndex !== -1) { + addToFilteredToolState(annotation, toolType); + uids.splice(uidIndex, 1); + + if (!uids.length) { + return filteredToolState; + } + } + } + } + } + } + + return filteredToolState; +} + +export default getFilteredCornerstoneToolState; diff --git a/extensions/dicom-sr/src/utils/isRehydratable.js b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js similarity index 63% rename from extensions/dicom-sr/src/utils/isRehydratable.js rename to extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js index cb95b288aec..44af7d27604 100644 --- a/extensions/dicom-sr/src/utils/isRehydratable.js +++ b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js @@ -1,6 +1,9 @@ import { adapters } from 'dcmjs'; -const cornerstoneAdapters = adapters.Cornerstone; +const cornerstoneAdapters = adapters.Cornerstone3D; + +const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; +const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG; /** * Checks if the given `displaySet`can be rehydrated into the `MeasurementService`. @@ -14,7 +17,7 @@ export default function isRehydratable(displaySet, mappings) { return false; } - const mappingDefinitions = mappings.map(m => m.definition); + const mappingDefinitions = mappings.map(m => m.annotationType); const { measurements } = displaySet; const adapterKeys = Object.keys(cornerstoneAdapters).filter( @@ -35,9 +38,18 @@ export default function isRehydratable(displaySet, mappings) { for (let i = 0; i < measurements.length; i++) { const TrackingIdentifier = measurements[i].TrackingIdentifier; - const hydratable = adapters.some(adapter => - adapter.isValidCornerstoneTrackingIdentifier(TrackingIdentifier) - ); + const hydratable = adapters.some(adapter => { + let [cornerstoneTag, toolName] = TrackingIdentifier.split(':'); + if (supportedLegacyCornerstoneTags.includes(cornerstoneTag)) { + cornerstoneTag = CORNERSTONE_3D_TAG; + } + + const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`; + + return adapter.isValidCornerstoneTrackingIdentifier( + mappedTrackingIdentifier + ); + }); if (hydratable) { return true; diff --git a/platform/core/src/DICOMSR/utils/isToolSupported.js b/extensions/cornerstone-dicom-sr/src/utils/isToolSupported.js similarity index 83% rename from platform/core/src/DICOMSR/utils/isToolSupported.js rename to extensions/cornerstone-dicom-sr/src/utils/isToolSupported.js index ad9dc651640..4e455c0d631 100644 --- a/platform/core/src/DICOMSR/utils/isToolSupported.js +++ b/extensions/cornerstone-dicom-sr/src/utils/isToolSupported.js @@ -7,7 +7,7 @@ import dcmjs from 'dcmjs'; * @returns {boolean} */ const isToolSupported = toolName => { - const adapter = dcmjs.adapters.Cornerstone; + const adapter = dcmjs.adapters.Cornerstone3D; return !!adapter[toolName]; }; diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx new file mode 100644 index 00000000000..680c51cdeaa --- /dev/null +++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx @@ -0,0 +1,452 @@ +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import OHIF, { utils } from '@ohif/core'; +import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule'; +import { + Notification, + ViewportActionBar, + useViewportGrid, + useViewportDialog, +} from '@ohif/ui'; + +const { formatDate } = utils; + +const MEASUREMENT_TRACKING_EXTENSION_ID = + '@ohif/extension-measurement-tracking'; + +const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; + +function OHIFCornerstoneSRViewport(props) { + const { + children, + dataSource, + displaySets, + viewportIndex, + viewportLabel, + servicesManager, + extensionManager, + } = props; + + const { + DisplaySetService, + CornerstoneViewportService, + } = servicesManager.services; + + // SR viewport will always have a single display set + if (displaySets.length > 1) { + throw new Error('SR viewport should only have a single display set'); + } + + const srDisplaySet = displaySets[0]; + + const [viewportGrid, viewportGridService] = useViewportGrid(); + const [viewportDialogState, viewportDialogApi] = useViewportDialog(); + const [measurementSelected, setMeasurementSelected] = useState(0); + const [measurementCount, setMeasurementCount] = useState(1); + const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState( + null + ); + const [ + referencedDisplaySetMetadata, + setReferencedDisplaySetMetadata, + ] = useState(null); + const [isHydrated, setIsHydrated] = useState(srDisplaySet.isHydrated); + const [element, setElement] = useState(null); + const { viewports, activeViewportIndex } = viewportGrid; + + // Optional hook into tracking extension, if present. + let trackedMeasurements; + let sendTrackedMeasurementsEvent; + + const hasMeasurementTrackingExtension = extensionManager.registeredExtensionIds.includes( + MEASUREMENT_TRACKING_EXTENSION_ID + ); + + // TODO: this is a hook that fails if we register/de-register + if (hasMeasurementTrackingExtension) { + const contextModule = extensionManager.getModuleEntry( + '@ohif/extension-measurement-tracking.contextModule.TrackedMeasurementsContext' + ); + + const useTrackedMeasurements = () => useContext(contextModule.context); + + [ + trackedMeasurements, + sendTrackedMeasurementsEvent, + ] = useTrackedMeasurements(); + } + + /** + * Store the tracking identifiers per viewport in order to be able to + * show the SR measurements on the referenced image on the correct viewport, + * when multiple viewports are used. + */ + const setTrackingIdentifiers = useCallback( + measurementSelected => { + const { measurements } = srDisplaySet; + + setTrackingUniqueIdentifiersForElement( + element, + measurements.map(measurement => measurement.TrackingUniqueIdentifier), + measurementSelected + ); + }, + [element, measurementSelected, srDisplaySet] + ); + + /** + * OnElementEnabled callback which is called after the cornerstoneExtension + * has enabled the element. Note: we delegate all the image rendering to + * cornerstoneExtension, so we don't need to do anything here regarding + * the image rendering, element enabling etc. + */ + const onElementEnabled = evt => { + setElement(evt.detail.element); + }; + + const updateViewport = useCallback( + newMeasurementSelected => { + const { + StudyInstanceUID, + displaySetInstanceUID, + sopClassUids, + } = srDisplaySet; + + if (!StudyInstanceUID || !displaySetInstanceUID) { + return; + } + + if (sopClassUids && sopClassUids.length > 1) { + // Todo: what happens if there are multiple SOP Classes? Why we are + // not throwing an error? + console.warn( + 'More than one SOPClassUID in the same series is not yet supported.' + ); + } + + _getViewportReferencedDisplaySetData( + srDisplaySet, + newMeasurementSelected, + DisplaySetService + ).then(({ referencedDisplaySet, referencedDisplaySetMetadata }) => { + setMeasurementSelected(newMeasurementSelected); + setActiveImageDisplaySetData(referencedDisplaySet); + setReferencedDisplaySetMetadata(referencedDisplaySetMetadata); + + if ( + referencedDisplaySet.displaySetInstanceUID === + activeImageDisplaySetData?.displaySetInstanceUID + ) { + const { measurements } = srDisplaySet; + + // it means that we have a new referenced display set, and the + // imageIdIndex will handle it by updating the viewport, but if they + // are the same we just need to use MeasurementService to jump to the + // new measurement + const viewportInfo = CornerstoneViewportService.getViewportInfoByIndex( + viewportIndex + ); + + const csViewport = CornerstoneViewportService.getCornerstoneViewport( + viewportInfo.getViewportId() + ); + + const imageIds = csViewport.getImageIds(); + + const imageIdIndex = imageIds.indexOf( + measurements[newMeasurementSelected].imageId + ); + + if (imageIdIndex !== -1) { + csViewport.setImageIdIndex(imageIdIndex); + } + } + }); + }, + [dataSource, srDisplaySet, activeImageDisplaySetData, viewportIndex] + ); + + const getCornerstoneViewport = useCallback(() => { + if (!activeImageDisplaySetData) { + return null; + } + + const { component: Component } = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.viewportModule.cornerstone' + ); + + const { measurements } = srDisplaySet; + const measurement = measurements[measurementSelected]; + + if (!measurement) { + return null; + } + + const initialImageIndex = activeImageDisplaySetData.images.findIndex( + image => image.imageId === measurement.imageId + ); + + return ( + + ); + }, [activeImageDisplaySetData, viewportIndex, measurementSelected]); + + const onMeasurementChange = useCallback( + direction => { + let newMeasurementSelected = measurementSelected; + + if (direction === 'right') { + newMeasurementSelected++; + + if (newMeasurementSelected >= measurementCount) { + newMeasurementSelected = 0; + } + } else { + newMeasurementSelected--; + + if (newMeasurementSelected < 0) { + newMeasurementSelected = measurementCount - 1; + } + } + + setTrackingIdentifiers(newMeasurementSelected); + updateViewport(newMeasurementSelected); + }, + [ + measurementSelected, + measurementCount, + updateViewport, + setTrackingIdentifiers, + ] + ); + + /** + Cleanup the SR viewport when the viewport is destroyed + */ + useEffect(() => { + const onDisplaySetsRemovedSubscription = DisplaySetService.subscribe( + DisplaySetService.EVENTS.DISPLAY_SETS_REMOVED, + ({ displaySetInstanceUIDs }) => { + const activeViewport = viewports[activeViewportIndex]; + if ( + displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) + ) { + viewportGridService.setDisplaySetsForViewport({ + viewportIndex: activeViewportIndex, + displaySetInstanceUIDs: [], + }); + } + } + ); + + return () => { + onDisplaySetsRemovedSubscription.unsubscribe(); + }; + }, []); + + /** + * Loading the measurements from the SR viewport, which goes through the + * isHydratable check, the outcome for the isHydrated state here is always FALSE + * since we don't do the hydration here. Todo: can't we just set it as false? why + * we are changing the state here? isHydrated is always false at this stage, and + * if it is hydrated we don't event use the SR viewport. + */ + useEffect(() => { + if (!srDisplaySet.isLoaded) { + srDisplaySet.load(); + } + setIsHydrated(srDisplaySet.isHydrated); + + const numMeasurements = srDisplaySet.measurements.length; + setMeasurementCount(numMeasurements); + }, [srDisplaySet]); + + /** + * Hook to update the tracking identifiers when the selected measurement changes or + * the element changes + */ + useEffect(() => { + if (!element || !srDisplaySet.isLoaded) { + return; + } + setTrackingIdentifiers(measurementSelected); + }, [measurementSelected, element, setTrackingIdentifiers, srDisplaySet]); + + /** + * Todo: what is this, not sure what it does regarding the react aspect, + * it is updating a local variable? which is not state. + */ + let isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0; + useEffect(() => { + isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0; + }, [trackedMeasurements]); + + /** + * Data fetching for the SR displaySet, which updates the measurements and + * also gets the referenced image displaySet that SR is based on. + */ + useEffect(() => { + updateViewport(measurementSelected); + }, [dataSource, srDisplaySet]); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + let childrenWithProps = null; + + if (!activeImageDisplaySetData || !referencedDisplaySetMetadata) { + return null; + } + + if (children && children.length) { + childrenWithProps = children.map((child, index) => { + return ( + child && + React.cloneElement(child, { + viewportIndex, + key: index, + }) + ); + }); + } + + const { Modality } = srDisplaySet; + + const { + PatientID, + PatientName, + PatientSex, + PatientAge, + SliceThickness, + ManufacturerModelName, + StudyDate, + SeriesDescription, + SpacingBetweenSlices, + SeriesNumber, + } = referencedDisplaySetMetadata; + + // TODO -> disabled double click for now: onDoubleClick={_onDoubleClick} + return ( + <> + { + evt.stopPropagation(); + evt.preventDefault(); + }} + onPillClick={() => { + sendTrackedMeasurementsEvent('RESTORE_PROMPT_HYDRATE_SR', { + displaySetInstanceUID: srDisplaySet.displaySetInstanceUID, + viewportIndex, + }); + }} + onSeriesChange={onMeasurementChange} + studyData={{ + label: viewportLabel, + useAltStyling: true, + isTracked: false, + isLocked, + isRehydratable: srDisplaySet.isRehydratable, + isHydrated, + studyDate: formatDate(StudyDate), + currentSeries: SeriesNumber, + seriesDescription: SeriesDescription, + modality: Modality, + patientInformation: { + patientName: PatientName + ? OHIF.utils.formatPN(PatientName.Alphabetic) + : '', + patientSex: PatientSex || '', + patientAge: PatientAge || '', + MRN: PatientID || '', + thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', + spacing: + SpacingBetweenSlices !== undefined + ? `${SpacingBetweenSlices.toFixed(2)}mm` + : '', + scanner: ManufacturerModelName || '', + }, + }} + /> + +
+ {getCornerstoneViewport()} +
+ {viewportDialogState.viewportIndex === viewportIndex && ( + + )} +
+ {childrenWithProps} +
+ + ); +} + +OHIFCornerstoneSRViewport.propTypes = { + displaySets: PropTypes.arrayOf(PropTypes.object), + viewportIndex: PropTypes.number.isRequired, + dataSource: PropTypes.object, + children: PropTypes.node, + customProps: PropTypes.object, +}; + +OHIFCornerstoneSRViewport.defaultProps = { + customProps: {}, +}; + +async function _getViewportReferencedDisplaySetData( + displaySet, + measurementSelected, + DisplaySetService +) { + const { measurements } = displaySet; + const measurement = measurements[measurementSelected]; + + const { displaySetInstanceUID } = measurement; + + const referencedDisplaySet = DisplaySetService.getDisplaySetByUID( + displaySetInstanceUID + ); + + const image0 = referencedDisplaySet.images[0]; + const referencedDisplaySetMetadata = { + PatientID: image0.PatientID, + PatientName: image0.PatientName, + PatientSex: image0.PatientSex, + PatientAge: image0.PatientAge, + SliceThickness: image0.SliceThickness, + StudyDate: image0.StudyDate, + SeriesDescription: image0.SeriesDescription, + SeriesInstanceUID: image0.SeriesInstanceUID, + SeriesNumber: image0.SeriesNumber, + ManufacturerModelName: image0.ManufacturerModelName, + SpacingBetweenSlices: image0.SpacingBetweenSlices, + }; + + return { referencedDisplaySetMetadata, referencedDisplaySet }; +} + +// function _onDoubleClick() { +// const cancelActiveManipulatorsForElement = cornerstoneTools.getModule( +// 'manipulatorState' +// ).setters.cancelActiveManipulatorsForElement; +// const enabledElements = cornerstoneTools.store.state.enabledElements; +// enabledElements.forEach(element => { +// cancelActiveManipulatorsForElement(element); +// }); +// } + +export default OHIFCornerstoneSRViewport; diff --git a/extensions/cornerstone/.webpack/webpack.dev.js b/extensions/cornerstone/.webpack/webpack.dev.js index db7c206b134..1ae30844802 100644 --- a/extensions/cornerstone/.webpack/webpack.dev.js +++ b/extensions/cornerstone/.webpack/webpack.dev.js @@ -1,5 +1,5 @@ const path = require('path'); -const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); const SRC_DIR = path.join(__dirname, '../src'); const DIST_DIR = path.join(__dirname, '../dist'); diff --git a/extensions/cornerstone/.webpack/webpack.prod.js b/extensions/cornerstone/.webpack/webpack.prod.js index a45f2b86be4..27e98c96f2a 100644 --- a/extensions/cornerstone/.webpack/webpack.prod.js +++ b/extensions/cornerstone/.webpack/webpack.prod.js @@ -1,9 +1,8 @@ const webpack = require('webpack'); -const { merge } = require('webpack-merge'); +const merge = require('webpack-merge'); const path = require('path'); -const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); const pkg = require('./../package.json'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const ROOT_DIR = path.join(__dirname, './..'); const SRC_DIR = path.join(__dirname, '../src'); @@ -13,7 +12,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, @@ -40,10 +38,6 @@ module.exports = (env, argv) => { new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, }), - new MiniCssExtractPlugin({ - filename: './dist/[name].css', - chunkFilename: './dist/[id].css', - }), ], }); }; diff --git a/extensions/cornerstone/CHANGELOG.md b/extensions/cornerstone/CHANGELOG.md index 26d422124a5..e69de29bb2d 100644 --- a/extensions/cornerstone/CHANGELOG.md +++ b/extensions/cornerstone/CHANGELOG.md @@ -1,593 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [2.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.2...@ohif/extension-cornerstone@2.7.3) (2020-05-12) - - -### Bug Fixes - -* 🐛 Fix seg color load ([#1724](https://github.com/OHIF/Viewers/issues/1724)) ([c4f84b1](https://github.com/OHIF/Viewers/commit/c4f84b1174d04ba84d37ed89b6d7ab541be28181)) - - - - - -## [2.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.1...@ohif/extension-cornerstone@2.7.2) (2020-05-04) - - -### Bug Fixes - -* 🐛 Proper error handling for derived display sets ([#1708](https://github.com/OHIF/Viewers/issues/1708)) ([5b20d8f](https://github.com/OHIF/Viewers/commit/5b20d8f323e4b3ef9988f2f2ab672d697b6da409)) - - - - - -## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.7.0...@ohif/extension-cornerstone@2.7.1) (2020-05-04) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.1...@ohif/extension-cornerstone@2.7.0) (2020-04-24) - - -### Features - -* 🎸 Seg jump to slice + show/hide ([835f64d](https://github.com/OHIF/Viewers/commit/835f64d47a9994f6a25aaf3941a4974e215e7e7f)) - - - - - -## [2.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.6.0...@ohif/extension-cornerstone@2.6.1) (2020-04-23) - - -### Bug Fixes - -* 🐛 Multiframe fix ([#1661](https://github.com/OHIF/Viewers/issues/1661)) ([7120561](https://github.com/OHIF/Viewers/commit/71205618ecb8b592247c5acb32284bfe7e18fce5)) - - - - - -# [2.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.5.2...@ohif/extension-cornerstone@2.6.0) (2020-04-23) - - -### Features - -* configuration to hook into XHR Error handling ([e96205d](https://github.com/OHIF/Viewers/commit/e96205de35e5bec14dc8a9a8509db3dd4e6ecdb6)) - - - - - -## [2.5.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.5.1...@ohif/extension-cornerstone@2.5.2) (2020-04-09) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [2.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.5.0...@ohif/extension-cornerstone@2.5.1) (2020-04-02) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# [2.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.3...@ohif/extension-cornerstone@2.5.0) (2020-03-13) - - -### Features - -* Segmentations Settings UI - Phase 1 [#1391](https://github.com/OHIF/Viewers/issues/1391) ([#1392](https://github.com/OHIF/Viewers/issues/1392)) ([e8842cf](https://github.com/OHIF/Viewers/commit/e8842cf8aebde98db7fc123e4867c8288552331f)), closes [#1423](https://github.com/OHIF/Viewers/issues/1423) - - - - - -## [2.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.2...@ohif/extension-cornerstone@2.4.3) (2020-03-09) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [2.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.1...@ohif/extension-cornerstone@2.4.2) (2020-03-09) - - -### Bug Fixes - -* Remove Eraser and ROI Window ([6c950a9](https://github.com/OHIF/Viewers/commit/6c950a9669f7fbf3c46e48679fa26ee514824156)) - - - - - -## [2.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.4.0...@ohif/extension-cornerstone@2.4.1) (2020-03-03) - - -### Bug Fixes - -* bump react-cornerstone-viewport version to address critical issue ([#1473](https://github.com/OHIF/Viewers/issues/1473)) ([ee80e02](https://github.com/OHIF/Viewers/commit/ee80e026610442e94caf5e4e3e4d193220cd0ece)) - - - - - -# [2.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.3.1...@ohif/extension-cornerstone@2.4.0) (2020-02-20) - - -### Features - -* [#1342](https://github.com/OHIF/Viewers/issues/1342) - Window level tab ([#1429](https://github.com/OHIF/Viewers/issues/1429)) ([ebc01a8](https://github.com/OHIF/Viewers/commit/ebc01a8ca238d5a3437b44d81f75aa8a5e8d0574)) - - - - - -## [2.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.3.0...@ohif/extension-cornerstone@2.3.1) (2020-02-14) - - -### Bug Fixes - -* Creating 2 commands to activate zoom tool and also to move between displaySets ([#1446](https://github.com/OHIF/Viewers/issues/1446)) ([06a4af0](https://github.com/OHIF/Viewers/commit/06a4af06faaecf6fa06ccd90cdfa879ee8d53053)) - - - - - -# [2.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.2...@ohif/extension-cornerstone@2.3.0) (2020-02-10) - - -### Features - -* 🎸 MeasurementService ([#1314](https://github.com/OHIF/Viewers/issues/1314)) ([0c37a40](https://github.com/OHIF/Viewers/commit/0c37a406d963569af8c3be24c697dafd42712dfc)) - - - - - -## [2.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.1...@ohif/extension-cornerstone@2.2.2) (2020-01-28) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.0...@ohif/extension-cornerstone@2.2.1) (2019-12-20) - - -### Bug Fixes - -* 🐛 1241: Make Plugin switch part of ToolbarModule ([#1322](https://github.com/OHIF/Viewers/issues/1322)) ([6540e36](https://github.com/OHIF/Viewers/commit/6540e36818944ac2eccc696186366ae495b33a04)), closes [#1241](https://github.com/OHIF/Viewers/issues/1241) - - - - - -# [2.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.1...@ohif/extension-cornerstone@2.2.0) (2019-12-20) - - -### Features - -* 🎸 Configuration so viewer tools can nix handles ([#1304](https://github.com/OHIF/Viewers/issues/1304)) ([63594d3](https://github.com/OHIF/Viewers/commit/63594d36b0bdba59f0901095aed70b75fb05172d)), closes [#1223](https://github.com/OHIF/Viewers/issues/1223) - - - - - -## [2.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.1.0...@ohif/extension-cornerstone@2.1.1) (2019-12-16) - - -### Bug Fixes - -* 🐛 add WwwcRegionTool to cornerstone tools initialization ([#1302](https://github.com/OHIF/Viewers/issues/1302)) ([d5bf728](https://github.com/OHIF/Viewers/commit/d5bf72851a32dff9fd3fc09332ea5250bc7e6114)) - - - - - -# [2.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.2...@ohif/extension-cornerstone@2.1.0) (2019-12-11) - - -### Features - -* 🎸 DICOM SR STOW on MeasurementAPI ([#954](https://github.com/OHIF/Viewers/issues/954)) ([ebe1af8](https://github.com/OHIF/Viewers/commit/ebe1af8d4f75d2483eba869655906d7829bd9666)), closes [#758](https://github.com/OHIF/Viewers/issues/758) - - - - - -## [2.0.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.1...@ohif/extension-cornerstone@2.0.2) (2019-12-11) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [2.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.0.0...@ohif/extension-cornerstone@2.0.1) (2019-12-09) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.2...@ohif/extension-cornerstone@2.0.0) (2019-12-09) - - -* feat!: Ability to configure cornerstone tools via extension configuration (#1229) ([55a5806](https://github.com/OHIF/Viewers/commit/55a580659ecb74ca6433461d8f9a05c2a2b69533)), closes [#1229](https://github.com/OHIF/Viewers/issues/1229) - - -### BREAKING CHANGES - -* modifies the exposed react components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance. - - - - - -## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.1...@ohif/extension-cornerstone@1.7.2) (2019-12-02) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.7.0...@ohif/extension-cornerstone@1.7.1) (2019-12-02) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.6.0...@ohif/extension-cornerstone@1.7.0) (2019-11-25) - - -### Features - -* Add new annotate tool using new dialog service ([#1211](https://github.com/OHIF/Viewers/issues/1211)) ([8fd3af1](https://github.com/OHIF/Viewers/commit/8fd3af1e137e793f1b482760a22591c64a072047)) - - - - - -# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.5.1...@ohif/extension-cornerstone@1.6.0) (2019-11-19) - - -### Features - -* New dialog service ([#1202](https://github.com/OHIF/Viewers/issues/1202)) ([f65639c](https://github.com/OHIF/Viewers/commit/f65639c2b0dab01decd20cab2cef4263cb4fab37)) - - - - - -## [1.5.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.5.0...@ohif/extension-cornerstone@1.5.1) (2019-11-15) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.1...@ohif/extension-cornerstone@1.5.0) (2019-11-13) - - -### Features - -* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255)) - - - - - -## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.0...@ohif/extension-cornerstone@1.4.1) (2019-11-08) - - -### Bug Fixes - -* Add a fallback metadata provider which pulls metadata from WADO-… ([#1158](https://github.com/OHIF/Viewers/issues/1158)) ([31b1adf](https://github.com/OHIF/Viewers/commit/31b1adfa5993d6c8e3e9c8b03fa9856f2621b037)) - - - - - -# [1.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.3.1...@ohif/extension-cornerstone@1.4.0) (2019-10-26) - - -### Features - -* Snapshot Download Tool ([#840](https://github.com/OHIF/Viewers/issues/840)) ([450e098](https://github.com/OHIF/Viewers/commit/450e0981a5ba054fcfcb85eeaeb18371af9088f8)) - - - - - -## [1.3.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.3.0...@ohif/extension-cornerstone@1.3.1) (2019-10-09) - - -### Bug Fixes - -* 🐛 set tools bidirectional, eraser and annotate command ([#1020](https://github.com/OHIF/Viewers/issues/1020)) ([a28984e](https://github.com/OHIF/Viewers/commit/a28984e)) - - - - - -# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.5...@ohif/extension-cornerstone@1.3.0) (2019-10-09) - - -### Features - -* Multiple fixes and implementation changes to react-cornerstone-viewport ([1cc94f3](https://github.com/OHIF/Viewers/commit/1cc94f3)) - - - - - -## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.4...@ohif/extension-cornerstone@1.2.5) (2019-09-27) - - -### Bug Fixes - -* version bump issue ([#963](https://github.com/OHIF/Viewers/issues/963)) ([e607ed2](https://github.com/OHIF/Viewers/commit/e607ed2)), closes [#962](https://github.com/OHIF/Viewers/issues/962) - - - - - -# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.2...@ohif/extension-cornerstone@2.0.0) (2019-09-27) - - -### Bug Fixes - -* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838) -* version bump issue ([#962](https://github.com/OHIF/Viewers/issues/962)) ([c80ea17](https://github.com/OHIF/Viewers/commit/c80ea17)) - - -### BREAKING CHANGES - -* DICOM Seg - - - - - -# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.2...@ohif/extension-cornerstone@2.0.0) (2019-09-27) - - -### Bug Fixes - -* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838) - - -### BREAKING CHANGES - -* DICOM Seg - - - - - -## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.1...@ohif/extension-cornerstone@1.2.2) (2019-09-26) - - -### Bug Fixes - -* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035)) - - - - - -## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.0...@ohif/extension-cornerstone@1.2.1) (2019-09-17) - - -### Bug Fixes - -* bump cornerstone-tools version in peerDeps ([4afc88c](https://github.com/OHIF/Viewers/commit/4afc88c)) - - - - - -# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.1.1...@ohif/extension-cornerstone@1.2.0) (2019-09-12) - - -### Features - -* **Annotate:** Add annotate tool back to toolbar ([26be967](https://github.com/OHIF/Viewers/commit/26be967)) - - - - - -## [1.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.1.0...@ohif/extension-cornerstone@1.1.1) (2019-09-12) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# [1.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.0.1...@ohif/extension-cornerstone@1.1.0) (2019-09-12) - - -### Features - -* **BidirectionalTool:** Add BidrectionalTool to "more" menu ([#911](https://github.com/OHIF/Viewers/issues/911)) ([e40cbae](https://github.com/OHIF/Viewers/commit/e40cbae)) -* **EraserTool:** add eraserTool to @ohif/extension-cornerstone ([#912](https://github.com/OHIF/Viewers/issues/912)) ([698d274](https://github.com/OHIF/Viewers/commit/698d274)) - - - - - -## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.0.0...@ohif/extension-cornerstone@1.0.1) (2019-09-10) - - -### Bug Fixes - -* simplify runtime-extension usage ([ac5dbda](https://github.com/OHIF/Viewers/commit/ac5dbda)) - - - - - -# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.50.5...@ohif/extension-cornerstone@1.0.0) (2019-09-09) - - -### Features - -* 🎸 Upgraded to cornerstoneTools 4.0 ([86adb51](https://github.com/OHIF/Viewers/commit/86adb51)) - - -### BREAKING CHANGES - -* n - - - - - -## [0.50.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.50.4...@ohif/extension-cornerstone@0.50.5) (2019-09-06) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [0.50.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.50.3...@ohif/extension-cornerstone@0.50.4) (2019-09-04) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [0.50.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.50.2...@ohif/extension-cornerstone@0.50.3) (2019-09-04) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [0.50.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.50.1...@ohif/extension-cornerstone@0.50.2) (2019-09-04) - - -### Bug Fixes - -* measurementsAPI issue caused by production build ([#842](https://github.com/OHIF/Viewers/issues/842)) ([49d3439](https://github.com/OHIF/Viewers/commit/49d3439)) - - - - - -## [0.50.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.50.0-alpha.10...@ohif/extension-cornerstone@0.50.1) (2019-08-14) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# [0.50.0-alpha.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.9...@ohif/extension-cornerstone@0.50.0-alpha.10) (2019-08-14) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## [0.0.39-alpha.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.8...@ohif/extension-cornerstone@0.0.39-alpha.9) (2019-08-14) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -## 0.0.39-alpha.8 (2019-08-14) - -**Note:** Version bump only for package @ohif/extension-cornerstone - - - - - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [0.0.39-alpha.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.6...@ohif/extension-cornerstone@0.0.39-alpha.7) (2019-08-08) - -**Note:** Version bump only for package @ohif/extension-cornerstone - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [0.0.39-alpha.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.5...@ohif/extension-cornerstone@0.0.39-alpha.6) (2019-08-08) - -**Note:** Version bump only for package @ohif/extension-cornerstone - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [0.0.39-alpha.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.4...@ohif/extension-cornerstone@0.0.39-alpha.5) (2019-08-08) - -**Note:** Version bump only for package @ohif/extension-cornerstone - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [0.0.39-alpha.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.3...@ohif/extension-cornerstone@0.0.39-alpha.4) (2019-08-08) - -**Note:** Version bump only for package @ohif/extension-cornerstone - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [0.0.39-alpha.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.2...@ohif/extension-cornerstone@0.0.39-alpha.3) (2019-08-08) - -**Note:** Version bump only for package @ohif/extension-cornerstone - -# Change Log - -All notable changes to this project will be documented in this file. See -[Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [0.0.39-alpha.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.1...@ohif/extension-cornerstone@0.0.39-alpha.2) (2019-08-07) - -**Note:** Version bump only for package @ohif/extension-cornerstone - -## [0.0.39-alpha.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@0.0.39-alpha.0...@ohif/extension-cornerstone@0.0.39-alpha.1) (2019-08-07) - -**Note:** Version bump only for package @ohif/extension-cornerstone - -## 0.0.39-alpha.0 (2019-08-05) - -**Note:** Version bump only for package @ohif/extension-cornerstone diff --git a/extensions/cornerstone/README.md b/extensions/cornerstone/README.md index d02e7483ef6..ded738c958b 100644 --- a/extensions/cornerstone/README.md +++ b/extensions/cornerstone/README.md @@ -1,123 +1 @@ -# @ohif/extension-cornerstone - -![npm (scoped)](https://img.shields.io/npm/v/@ohif/extension-cornerstone.svg?style=flat-square) - -This extension adds support for viewing and manipulating 2D medical images via a -viewport. The underlying implementation wraps the -`cornerstonejs/react-cornerstone-viewport`, and provides basic commands and -toolbar buttons for common actions. - - - -#### Index - -Extension Id: `cornerstone` - -- [Commands Module](#commands-module) -- [Toolbar Module](#toolbar-module) -- [Viewport Module](#viewport-module) - -## Commands Module - -This extensions includes the following `Commands` and `Command Definitions`. -These can be registered with `@ohif/core`'s `CommandManager`. After registering -the commands, they can be bound to `hotkeys` using the `HotkeysManager` and -listed in the `UserPreferences` modal. - -You can read more about [`Commands`][docs-commands], [`Hotkeys`][docs-hotkeys], -and the [`UserPreferences` Modal][docs-userprefs] in their respective locations -in the OHIF Viewer's documentation. - -| Command Name | Description | Store Contexts | -| ---------------------------- | --------------------------------------- | -------------- | -| `rotateViewportCW` | | viewports | -| `rotateViewportCCW` | | viewports | -| `invertViewport` | | viewports | -| `flipViewportVertical` | | viewports | -| `flipViewportHorizontal` | | viewports | -| `scaleUpViewport` | | viewports | -| `scaleDownViewport` | | viewports | -| `fitViewportToWindow` | | viewports | -| `resetViewport` | | viewports | -| clearAnnotations | TODO | | -| next/previous Image | TODO | | -| first/last Image | TODO | | -| `nextViewportDisplaySet` | | | -| `previousViewportDisplaySet` | | | -| `setToolActive` | Activates tool for primary button/touch | | - -## Toolbar Module - -Our toolbar module contains definitions for: - -- `StackScroll` -- `Zoom` -- `Wwwc` -- `Pan` -- `Length` -- `Angle` -- `Reset` -- `Cine` - -All use the `ACTIVE_VIEWPORT::CORNERSTONE` context. - -## Viewport Module - -Our Viewport wraps [cornerstonejs/react-cornerstone-viewport][react-viewport]. -This module is the most prone to change as we hammer out our Viewport interface. - -## Tool Configuration - -Tools can be configured through extension configuration using the tools key: - -```js - ... - cornerstoneExtensionConfig: { - tools: { - ArrowAnnotate: { - configuration: { - getTextCallback: (callback, eventDetails) => callback(prompt('Enter your custom annotation')), - }, - }, - }, - }, - ... -``` - -## Annotate Tools Configuration - -_We currently support one property for annotation tools._ - -### Hide handles - -This extension configuration allows you to toggle on/off handle rendering for -all annotate tools: - -```js - ... - cornerstoneExtensionConfig: { - hideHandles: true, - }, - ... - -## Resources - -### Repositories - -- [cornerstonejs/react-cornerstone-viewport][react-viewport] -- [cornerstonejs/cornerstoneTools][cornerstone-tools] -- [cornerstonejs/cornerstone][cornerstone] - - - - -[docs-commands]: https://www.com -[docs-hotkeys]: https://www.com -[docs-userprefs]: htt -[react-viewport]: https://github.com/cornerstonejs/react-cornerstone-viewport -[cornerstone-tools]: https://github.com/cornerstonejs/cornerstoneTools -[cornerstone]: https://github.com/cornerstonejs/cornerstone - -``` +# Cornerstone diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 918561a15de..e95a5c345d8 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -6,9 +6,9 @@ "license": "MIT", "repository": "OHIF/Viewers", "main": "dist/index.umd.js", - "module": "src/index.js", + "module": "src/index.tsx", "engines": { - "node": ">=14", + "node": ">=10", "npm": ">=6", "yarn": ">=1.16.0" }, @@ -16,9 +16,6 @@ "dist", "README.md" ], - "keywords": [ - "ohif-extension" - ], "publishConfig": { "access": "public" }, @@ -27,29 +24,33 @@ "dev:cornerstone": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package": "yarn run build", - "start": "yarn run dev", - "test:unit": "jest --watchAll", - "test:unit:ci": "jest --ci --runInBand --collectCoverage" + "start": "yarn run dev" }, "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "cornerstone-core": "2.6.0", - "cornerstone-math": "0.1.9", - "cornerstone-tools": "6.0.2", - "cornerstone-wado-image-loader": "4.0.4", - "dcmjs": "0.16.1", + "cornerstone-wado-image-loader": "^4.2.0", + "dcmjs": "^0.24.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-resize-detector": "^3.4.0" + "react-resize-detector": "^6.7.6" + }, + "devDependencies": { + "lodash": "4.17.21" }, "dependencies": { - "@babel/runtime": "7.16.3", - "lodash.merge": "^4.6.2", + "@babel/runtime": "7.17.9", + "@cornerstonejs/core": "^0.13.11", + "@cornerstonejs/streaming-image-volume-loader": "^0.4.10", + "@cornerstonejs/tools": "^0.20.15", + "@kitware/vtk.js": "^24.18.7", + "dom-to-image": "^2.6.0", "lodash.debounce": "4.0.8", - "react-cornerstone-viewport": "4.1.2" + "lodash.merge": "^4.6.2", + "shader-loader": "^1.3.1", + "worker-loader": "^3.0.8" } } diff --git a/extensions/cornerstone/src/CornerstoneViewportDownloadForm.js b/extensions/cornerstone/src/CornerstoneViewportDownloadForm.js deleted file mode 100644 index 684b1b837f2..00000000000 --- a/extensions/cornerstone/src/CornerstoneViewportDownloadForm.js +++ /dev/null @@ -1,152 +0,0 @@ -import React from 'react'; -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; -import PropTypes from 'prop-types'; - -import { ViewportDownloadForm } from '@ohif/ui'; -import { utils } from '@ohif/core'; - -import { getEnabledElement } from './state'; - -const MINIMUM_SIZE = 100; -const DEFAULT_SIZE = 512; -const MAX_TEXTURE_SIZE = 10000; - -const CornerstoneViewportDownloadForm = ({ onClose, activeViewportIndex }) => { - const enabledElement = getEnabledElement( - activeViewportIndex - ); - const activeEnabledElement = enabledElement?.element - - const enableViewport = viewportElement => { - if (viewportElement) { - cornerstone.enable(viewportElement); - } - }; - - const disableViewport = viewportElement => { - if (viewportElement) { - cornerstone.disable(viewportElement); - } - }; - - const updateViewportPreview = (viewportElement, downloadCanvas, fileType) => - new Promise(resolve => { - cornerstone.fitToWindow(viewportElement); - - viewportElement.addEventListener( - 'cornerstoneimagerendered', - function updateViewport(event) { - const enabledElement = cornerstone.getEnabledElement(event.target) - .element; - const type = 'image/' + fileType; - const dataUrl = downloadCanvas.toDataURL(type, 1); - - let newWidth = enabledElement.offsetHeight; - let newHeight = enabledElement.offsetWidth; - - if (newWidth > DEFAULT_SIZE || newHeight > DEFAULT_SIZE) { - const multiplier = DEFAULT_SIZE / Math.max(newWidth, newHeight); - newHeight *= multiplier; - newWidth *= multiplier; - } - - resolve({ dataUrl, width: newWidth, height: newHeight }); - - viewportElement.removeEventListener( - 'cornerstoneimagerendered', - updateViewport - ); - } - ); - }); - - const loadImage = (activeViewport, viewportElement, width, height) => - new Promise(resolve => { - if (activeViewport && viewportElement) { - const enabledElement = cornerstone.getEnabledElement(activeViewport); - const viewport = Object.assign({}, enabledElement.viewport); - delete viewport.scale; - viewport.translation = { - x: 0, - y: 0, - }; - - cornerstone.loadImage(enabledElement.image.imageId).then(image => { - cornerstone.displayImage(viewportElement, image); - cornerstone.setViewport(viewportElement, viewport); - cornerstone.resize(viewportElement, true); - - const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE); - const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE); - - resolve({ image, width: newWidth, height: newHeight }); - }); - } - }); - - const toggleAnnotations = (toggle, viewportElement) => { - cornerstoneTools.store.state.tools.forEach(({ name }) => { - if (toggle) { - cornerstoneTools.setToolEnabledForElement(viewportElement, name); - } else { - cornerstoneTools.setToolDisabledForElement(viewportElement, name); - } - }); - }; - - const downloadBlob = ( - filename, - fileType, - viewportElement, - downloadCanvas - ) => { - const file = `${filename}.${fileType}`; - const mimetype = `image/${fileType}`; - - /* Handles JPEG images for IE11 */ - if (downloadCanvas.msToBlob && fileType === 'jpeg') { - const image = downloadCanvas.toDataURL(mimetype, 1); - const blob = utils.b64toBlob( - image.replace('data:image/jpeg;base64,', ''), - mimetype - ); - return window.navigator.msSaveBlob(blob, file); - } - - viewportElement.querySelector('canvas').toBlob(blob => { - const URLObj = window.URL || window.webkitURL; - const a = document.createElement('a'); - - a.href = URLObj.createObjectURL(blob); - a.download = file; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - }); - }; - - return ( - - ); -}; - -CornerstoneViewportDownloadForm.propTypes = { - onClose: PropTypes.func, - activeViewportIndex: PropTypes.number.isRequired, -}; - -export default CornerstoneViewportDownloadForm; diff --git a/extensions/cornerstone/src/OHIFCornerstoneViewport.js b/extensions/cornerstone/src/OHIFCornerstoneViewport.js deleted file mode 100644 index f1dd4197c5b..00000000000 --- a/extensions/cornerstone/src/OHIFCornerstoneViewport.js +++ /dev/null @@ -1,398 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; -import CornerstoneViewport from 'react-cornerstone-viewport'; -import OHIF from '@ohif/core'; -import { useCine, useViewportGrid } from '@ohif/ui'; - -import ViewportLoadingIndicator from './ViewportLoadingIndicator'; -import setCornerstoneMeasurementActive from './_shared/setCornerstoneMeasurementActive'; -import setActiveAndPassiveToolsForElement from './_shared/setActiveAndPassiveToolsForElement'; -import getTools from './_shared/getTools'; - -import ViewportOverlay from './ViewportOverlay'; - -const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex'); - -const BaseAnnotationTool = cornerstoneTools.importInternal( - 'base/BaseAnnotationTool' -); - -const { StackManager } = OHIF.utils; - -function OHIFCornerstoneViewport({ - children, - dataSource, - displaySet, - onElementEnabled, - element, - viewportIndex, - servicesManager, - commandsManager, -}) { - const { - ToolBarService, - DisplaySetService, - MeasurementService, - HangingProtocolService, - } = servicesManager.services; - const [viewportData, setViewportData] = useState(null); - const [{ cines }, cineService] = useCine(); - const [{ viewports }, viewportGridService] = useViewportGrid(); - - const isMounted = useRef(false); - const stageChangedRef = useRef(false); - - const onNewImage = (element, callback) => { - const handler = () => { - element.removeEventListener(cornerstone.EVENTS.IMAGE_RENDERED, handler); - callback(element, ToolBarService); - }; - element.addEventListener(cornerstone.EVENTS.IMAGE_RENDERED, handler); - }; - - const defaultOnElementEnabled = evt => { - const eventData = evt.detail; - const targetElement = eventData.element; - const tools = getTools(); - const toolAlias = ToolBarService.state.primaryToolId; - - // Activate appropriate tool bindings for element - setActiveAndPassiveToolsForElement(targetElement, tools); - cornerstoneTools.setToolActiveForElement(targetElement, toolAlias, { - mouseButtonMask: 1, - }); - - // Set dashed, based on tracking, for this viewport - const allTools = cornerstoneTools.store.state.tools; - const toolsForElement = allTools.filter( - tool => tool.element === targetElement - ); - - toolsForElement.forEach(tool => { - if (tool instanceof BaseAnnotationTool) { - const configuration = tool.configuration; - - configuration.renderDashed = true; - - tool.configuration = configuration; - } - }); - - // Update image after setting tool config - const enabledElement = cornerstone.getEnabledElement(targetElement); - - if (enabledElement.image) { - cornerstone.updateImage(targetElement); - } - }; - - useEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - StackManager.clearStacks(); - }; - }, []); - - useEffect(() => { - const { unsubscribe } = HangingProtocolService.subscribe( - HangingProtocolService.EVENTS.STAGE_CHANGE, - () => { - stageChangedRef.current = true; - } - ); - return () => { - unsubscribe(); - }; - }, []); - - useEffect(() => { - cineService.setCine({ id: viewportIndex }); - }, [viewportIndex]); - - useEffect(() => { - const { - StudyInstanceUID, - displaySetInstanceUID, - sopClassUids, - } = displaySet; - - if (!StudyInstanceUID || !displaySetInstanceUID) { - return; - } - - if (sopClassUids && sopClassUids.length > 1) { - console.warn( - 'More than one SOPClassUID in the same series is not yet supported.' - ); - } - - _getViewportData(dataSource, displaySet).then(data => { - if (isMounted.current) setViewportData(data); - }); - }, [dataSource, displaySet, viewportIndex]); - - useEffect(() => { - const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents( - MeasurementService, - DisplaySetService, - element, - viewportIndex, - displaySet.displaySetInstanceUID, - viewportGridService - ); - - _checkForCachedJumpToMeasurementEvents( - MeasurementService, - DisplaySetService, - element, - viewportIndex, - displaySet.displaySetInstanceUID, - viewportGridService - ); - - // reseting the HP stage changed flag - if (element) { - onNewImage(element, () => { - stageChangedRef.current = false; - }); - } - - // running HP-defined callbacks: invert, window level ... - if (element && displaySet.renderedCallback) { - onNewImage(element, displaySet.renderedCallback); - } - - return () => { - unsubscribeFromJumpToMeasurementEvents(); - }; - }, [element, displaySet]); - - let childrenWithProps = null; - - if (!viewportData) { - return null; - } - - const { - imageIds, - initialImageIdIndex, - // If this comes from the instance, would be a better default - // `FrameTime` in the instance - // frameRate = 0, - } = viewportData.stack; - - // TODO: Does it make more sense to use Context? - if (children && children.length) { - childrenWithProps = children.map((child, index) => { - return ( - child && - React.cloneElement(child, { - viewportIndex, - key: index, - }) - ); - }); - } - - // We have... - // StudyInstanceUid, DisplaySetInstanceUid - // Use displaySetInstanceUid --> SeriesInstanceUid - // Get meta for series, map to actionBar - // const displaySet = DisplaySetService.getDisplaySetByUID( - // dSet.displaySetInstanceUID - // ); - // TODO: This display contains the meta for all instances. - // That can't be right... - // console.log('DISPLAYSET', displaySet); - // const seriesMeta = DicomMetadataStore.getSeries(this.props.displaySet.StudyInstanceUID, ''); - // console.log(seriesMeta); - - const cine = cines[viewportIndex]; - const isPlaying = (cine && cine.isPlaying) || false; - const frameRate = (cine && cine.frameRate) || 24; - - return ( -
- { - return ( - - ); - }} - /> - {childrenWithProps} -
- ); -} - -OHIFCornerstoneViewport.propTypes = { - displaySet: PropTypes.object, - viewportIndex: PropTypes.number, - dataSource: PropTypes.object, - children: PropTypes.node, - customProps: PropTypes.object, - ToolBarService: PropTypes.object, -}; - -OHIFCornerstoneViewport.defaultProps = { - customProps: {}, -}; - -const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; - -function _getCornerstoneStack(displaySet, dataSource) { - // Get stack from Stack Manager - const storedStack = StackManager.findOrCreateStack(displaySet, dataSource); - - // Clone the stack here so we don't mutate it - const stack = Object.assign({}, storedStack); - - return stack; -} - -async function _getViewportData(dataSource, displaySet) { - const stack = _getCornerstoneStack(displaySet, dataSource); - - const viewportData = { - StudyInstanceUID: displaySet.StudyInstanceUID, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - stack, - }; - - return viewportData; -} - -function _subscribeToJumpToMeasurementEvents( - MeasurementService, - DisplaySetService, - element, - viewportIndex, - displaySetInstanceUID, - viewportGridService -) { - const { unsubscribe } = MeasurementService.subscribe( - MeasurementService.EVENTS.JUMP_TO_MEASUREMENT, - ({ measurement }) => { - if (!measurement) return; - // check if the correct viewport index. - // if (viewportIndex !== jumpToMeasurementViewportIndex) { - // // Event for a different viewport. - // return; - // } - - // Jump the the measurement if the displaySetInstanceUID matches - if (measurement.displaySetInstanceUID === displaySetInstanceUID) { - _jumpToMeasurement( - measurement, - element, - viewportIndex, - MeasurementService, - DisplaySetService, - viewportGridService - ); - } - } - ); - - return unsubscribe; -} - -function _checkForCachedJumpToMeasurementEvents( - MeasurementService, - DisplaySetService, - element, - viewportIndex, - displaySetInstanceUID, - viewportGridService -) { - // Check if there is a queued jumpToMeasurement event - const measurementIdToJumpTo = MeasurementService.getJumpToMeasurement( - viewportIndex - ); - - if (measurementIdToJumpTo && element) { - // Jump to measurement if the measurement exists - const measurement = MeasurementService.getMeasurement( - measurementIdToJumpTo - ); - - if (measurement.displaySetInstanceUID === displaySetInstanceUID) { - _jumpToMeasurement( - measurement, - element, - viewportIndex, - MeasurementService, - DisplaySetService, - viewportGridService - ); - } - } -} - -function _jumpToMeasurement( - measurement, - targetElement, - viewportIndex, - MeasurementService, - DisplaySetService, - viewportGridService -) { - const { displaySetInstanceUID, SOPInstanceUID } = measurement; - - const referencedDisplaySet = DisplaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); - - const imageIndex = referencedDisplaySet.images.findIndex( - i => i.SOPInstanceUID === SOPInstanceUID - ); - - setCornerstoneMeasurementActive(measurement); - viewportGridService.setActiveViewportIndex(viewportIndex); - if (targetElement !== null) { - const enabledElement = cornerstone.getEnabledElement(targetElement); - - // Wait for the image to update or we get a race condition when the element has only just been enabled. - const scrollToHandler = evt => { - scrollToIndex(targetElement, imageIndex); - targetElement.removeEventListener( - 'cornerstoneimagerendered', - scrollToHandler - ); - }; - targetElement.addEventListener('cornerstoneimagerendered', scrollToHandler); - - if (enabledElement.image) { - cornerstone.updateImage(targetElement); - } - - // Jump to measurement consumed, remove. - MeasurementService.removeJumpToMeasurement(viewportIndex); - } -} - -export default OHIFCornerstoneViewport; diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.css b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.css new file mode 100644 index 00000000000..5380ba303c0 --- /dev/null +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.css @@ -0,0 +1,19 @@ +.viewport-wrapper { + width: 100%; + height: 100%; /* MUST have `height` to prevent resize infinite loop */ + position: relative; +} + +.cornerstone-viewport-element { + width: 100%; + height: 100%; + position: relative; + background-color: black; + + /* Prevent the blue outline in Chrome when a viewport is selected */ + outline: 0 !important; + + /* Prevents the entire page from getting larger + when the magnify tool is near the sides/corners of the page */ + overflow: hidden; +} diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx new file mode 100644 index 00000000000..d193f4f6bb7 --- /dev/null +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -0,0 +1,432 @@ +import React, { useEffect, useRef, useCallback, useState } from 'react'; +import ReactResizeDetector from 'react-resize-detector'; +import PropTypes from 'prop-types'; +import { useViewportGrid } from '@ohif/ui'; +import * as cs3DTools from '@cornerstonejs/tools'; +import { Enums, eventTarget, getEnabledElement } from '@cornerstonejs/core'; + +import { setEnabledElement } from '../state'; +import CornerstoneCacheService from '../services/ViewportService/CornerstoneCacheService'; + +import './OHIFCornerstoneViewport.css'; +import CornerstoneOverlays from './Overlays/CornerstoneOverlays'; + +const STACK = 'stack'; + +function areEqual(prevProps, nextProps) { + if (nextProps.needsRerendering) { + return false; + } + + if (prevProps.displaySets.length !== nextProps.displaySets.length) { + return false; + } + + const prevDisplaySets = prevProps.displaySets[0]; + const nextDisplaySets = nextProps.displaySets[0]; + + if (prevDisplaySets && nextDisplaySets) { + const areSameDisplaySetInstanceUIDs = + prevDisplaySets.displaySetInstanceUID === + nextDisplaySets.displaySetInstanceUID; + const areSameImageLength = + prevDisplaySets.images.length === nextDisplaySets.images.length; + const areSameImageIds = prevDisplaySets.images.every( + (prevImage, index) => + prevImage.imageId === nextDisplaySets.images[index].imageId + ); + return ( + areSameDisplaySetInstanceUIDs && areSameImageLength && areSameImageIds + ); + } + return false; +} + +// Todo: This should be done with expose of internal API similar to react-vtkjs-viewport +// Then we don't need to worry about the re-renders if the props change. +const OHIFCornerstoneViewport = React.memo(props => { + const { + viewportIndex, + displaySets, + dataSource, + viewportOptions, + displaySetOptions, + servicesManager, + onElementEnabled, + onElementDisabled, + // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation + // of the imageData in the OHIFCornerstoneViewport. This prop is used + // to set the initial state of the viewport's first image to render + initialImageIndex, + } = props; + + const [scrollbarHeight, setScrollbarHeight] = useState('100px'); + const [viewportData, setViewportData] = useState(null); + const [_, viewportGridService] = useViewportGrid(); + + const elementRef = useRef(); + + const { + MeasurementService, + DisplaySetService, + ToolBarService, + ToolGroupService, + SyncGroupService, + CornerstoneViewportService, + } = servicesManager.services; + + // useCallback for scroll bar height calculation + const setImageScrollBarHeight = useCallback(() => { + const scrollbarHeight = `${elementRef.current.clientHeight - 20}px`; + setScrollbarHeight(scrollbarHeight); + }, [elementRef]); + + // useCallback for onResize + const onResize = useCallback(() => { + if (elementRef.current) { + CornerstoneViewportService.resize(); + setImageScrollBarHeight(); + } + }, [elementRef]); + + const elementEnabledHandler = useCallback( + evt => { + // check this is this element reference and return early if doesn't match + if (evt.detail.element !== elementRef.current) { + return; + } + + const { viewportId, element } = evt.detail; + const viewportInfo = CornerstoneViewportService.getViewportInfo( + viewportId + ); + const viewportIndex = viewportInfo.getViewportIndex(); + + setEnabledElement(viewportIndex, element); + + const renderingEngineId = viewportInfo.getRenderingEngineId(); + const toolGroupId = viewportInfo.getToolGroupId(); + const syncGroups = viewportInfo.getSyncGroups(); + + ToolGroupService.addViewportToToolGroup( + viewportId, + renderingEngineId, + toolGroupId + ); + + SyncGroupService.addViewportToSyncGroup( + viewportId, + renderingEngineId, + syncGroups + ); + + if (onElementEnabled) { + onElementEnabled(evt); + } + }, + [viewportIndex, onElementEnabled, ToolGroupService] + ); + + // disable the element upon unmounting + useEffect(() => { + CornerstoneViewportService.enableElement( + viewportIndex, + viewportOptions, + elementRef.current + ); + + eventTarget.addEventListener( + Enums.Events.ELEMENT_ENABLED, + elementEnabledHandler + ); + + setImageScrollBarHeight(); + + return () => { + const viewportInfo = CornerstoneViewportService.getViewportInfoByIndex( + viewportIndex + ); + + const viewportId = viewportInfo.getViewportId(); + const renderingEngineId = viewportInfo.getRenderingEngineId(); + const syncGroups = viewportInfo.getSyncGroups(); + + ToolGroupService.disable(viewportId, renderingEngineId); + SyncGroupService.removeViewportFromSyncGroup( + viewportId, + renderingEngineId, + syncGroups + ); + + CornerstoneViewportService.disableElement(viewportIndex); + + eventTarget.removeEventListener( + Enums.Events.ELEMENT_ENABLED, + elementEnabledHandler + ); + + if (onElementDisabled) { + onElementDisabled(); + } + }; + }, []); + + // subscribe to displaySet metadata invalidation (updates) + // Currently, if the metadata changes we need to re-render the display set + // for it to take effect in the viewport. As we deal with scaling in the loading, + // we need to remove the old volume from the cache, and let the + // viewport to re-add it which will use the new metadata. Otherwise, the + // viewport will use the cached volume and the new metadata will not be used. + // Note: this approach does not actually end of sending network requests + // and it uses the network cache + useEffect(() => { + const { unsubscribe } = DisplaySetService.subscribe( + DisplaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, + async invalidatedDisplaySetInstanceUID => { + if ( + viewportData.displaySetInstanceUIDs.includes( + invalidatedDisplaySetInstanceUID + ) + ) { + const newViewportData = await CornerstoneCacheService.invalidateViewportData( + viewportData, + invalidatedDisplaySetInstanceUID, + dataSource, + DisplaySetService + ); + + CornerstoneViewportService.updateViewport( + viewportIndex, + newViewportData + ); + + setViewportData(newViewportData); + } + } + ); + return () => { + unsubscribe(); + }; + }, [viewportData, viewportIndex]); + + useEffect(() => { + // handle the default viewportType to be stack + if (!viewportOptions.viewportType) { + viewportOptions.viewportType = STACK; + } + + const loadViewportData = async () => { + const viewportData = await CornerstoneCacheService.getViewportData( + viewportIndex, + displaySets, + viewportOptions.viewportType, + dataSource, + initialImageIndex + ); + + CornerstoneViewportService.setViewportDisplaySets( + viewportIndex, + viewportData, + viewportOptions, + displaySetOptions + ); + + setViewportData(viewportData); + }; + + loadViewportData(); + }, [viewportOptions, displaySets, dataSource]); + + /** + * There are two scenarios for jump to click + * 1. Current viewports contain the displaySet that the annotation was drawn on + * 2. Current viewports don't contain the displaySet that the annotation was drawn on + * and we need to change the viewports displaySet for jumping. + * Since measurement_jump happens via events and listeners, the former case is handled + * by the measurement_jump direct callback, but the latter case is handled first by + * the viewportGrid to set the correct displaySet on the viewport, AND THEN we check + * the cache for jumping to see if there is any jump queued, then we jump to the correct slice. + */ + useEffect(() => { + const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + elementRef, + viewportIndex, + displaySets, + viewportGridService + ); + + _checkForCachedJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + elementRef, + viewportIndex, + displaySets, + viewportGridService + ); + + return () => { + unsubscribeFromJumpToMeasurementEvents(); + }; + }, [displaySets, elementRef, viewportIndex, viewportData]); + + return ( +
+ +
e.preventDefault()} + onMouseDown={e => e.preventDefault()} + ref={elementRef} + >
+ +
+ ); +}, areEqual); + +function _subscribeToJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + elementRef, + viewportIndex, + displaySets, + viewportGridService +) { + const displaysUIDs = displaySets.map( + displaySet => displaySet.displaySetInstanceUID + ); + const { unsubscribe } = MeasurementService.subscribe( + MeasurementService.EVENTS.JUMP_TO_MEASUREMENT, + ({ measurement }) => { + if (!measurement) return; + + // Jump the the measurement if the viewport contains the displaySetUID (fusion) + if (displaysUIDs.includes(measurement.displaySetInstanceUID)) { + _jumpToMeasurement( + measurement, + elementRef, + viewportIndex, + MeasurementService, + DisplaySetService, + viewportGridService + ); + } + } + ); + + return unsubscribe; +} + +// Check if there is a queued jumpToMeasurement event +function _checkForCachedJumpToMeasurementEvents( + MeasurementService, + DisplaySetService, + elementRef, + viewportIndex, + displaySets, + viewportGridService +) { + const displaysUIDs = displaySets.map( + displaySet => displaySet.displaySetInstanceUID + ); + + const measurementIdToJumpTo = MeasurementService.getJumpToMeasurement( + viewportIndex + ); + + if (measurementIdToJumpTo && elementRef) { + // Jump to measurement if the measurement exists + const measurement = MeasurementService.getMeasurement( + measurementIdToJumpTo + ); + + if (displaysUIDs.includes(measurement.displaySetInstanceUID)) { + _jumpToMeasurement( + measurement, + elementRef, + viewportIndex, + MeasurementService, + DisplaySetService, + viewportGridService + ); + } + } +} + +function _jumpToMeasurement( + measurement, + targetElementRef, + viewportIndex, + MeasurementService, + DisplaySetService, + viewportGridService +) { + const targetElement = targetElementRef.current; + const { displaySetInstanceUID, SOPInstanceUID } = measurement; + + if (!SOPInstanceUID) { + console.warn('cannot jump in a non-acquisition plane measurements yet'); + } + + const referencedDisplaySet = DisplaySetService.getDisplaySetByUID( + displaySetInstanceUID + ); + + const imageIdIndex = referencedDisplaySet.images.findIndex( + i => i.SOPInstanceUID === SOPInstanceUID + ); + + // Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager + // to set it properly + // setCornerstoneMeasurementActive(measurement); + + viewportGridService.setActiveViewportIndex(viewportIndex); + + if (getEnabledElement(targetElement)) { + cs3DTools.utilities.jumpToSlice(targetElement, { + imageIndex: imageIdIndex, + }); + + cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid); + // Jump to measurement consumed, remove. + MeasurementService.removeJumpToMeasurement(viewportIndex); + } +} + +// Component displayName +OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport'; + +OHIFCornerstoneViewport.propTypes = { + viewportIndex: PropTypes.number.isRequired, + displaySets: PropTypes.array.isRequired, + dataSource: PropTypes.object.isRequired, + viewportOptions: PropTypes.object, + displaySetOptions: PropTypes.arrayOf(PropTypes.object), + servicesManager: PropTypes.object.isRequired, + onElementEnabled: PropTypes.func, + // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation + // of the imageData in the OHIFCornerstoneViewport. This prop is used + // to set the initial state of the viewport's first image to render + initialImageIdOrIndex: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), +}; + +export default OHIFCornerstoneViewport; diff --git a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx new file mode 100644 index 00000000000..b3fbf9f6b91 --- /dev/null +++ b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; + +import ViewportImageScrollbar from './ViewportImageScrollbar'; +import ViewportOverlay from './ViewportOverlay'; +import ViewportOrientationMarkers from './ViewportOrientationMarkers'; +import ViewportLoadingIndicator from './ViewportLoadingIndicator'; +import CornerstoneCacheService from '../../services/ViewportService/CornerstoneCacheService'; + +function CornerstoneOverlays(props) { + const { viewportIndex, element, scrollbarHeight, servicesManager } = props; + const { CornerstoneViewportService } = servicesManager.services; + const [imageSliceData, setImageSliceData] = useState({ + imageIndex: 0, + numberOfSlices: 0, + }); + const [viewportData, setViewportData] = useState(null); + + useEffect(() => { + const { unsubscribe } = CornerstoneCacheService.subscribe( + CornerstoneCacheService.EVENTS.VIEWPORT_DATA_CHANGED, + props => { + if (props.viewportIndex !== viewportIndex) { + return; + } + + setViewportData(props.viewportData); + } + ); + + return () => { + unsubscribe(); + }; + }, [viewportIndex]); + + if (!element) { + return null; + } + + if (viewportData) { + const viewportInfo = CornerstoneViewportService.getViewportInfoByIndex( + viewportIndex + ); + + if (viewportInfo?.viewportOptions?.customViewportOptions?.hideOverlays) { + return null; + } + } + + return ( +
+ + + + +
+ ); +} + +export default CornerstoneOverlays; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx new file mode 100644 index 00000000000..b061b60d020 --- /dev/null +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx @@ -0,0 +1,145 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { Enums, Types, utilities } from '@cornerstonejs/core'; +import { utilities as csToolsUtils } from '@cornerstonejs/tools'; +import { ImageScrollbar } from '@ohif/ui'; + +function CornerstoneImageScrollbar({ + viewportData, + viewportIndex, + element, + imageSliceData, + setImageSliceData, + scrollbarHeight, + servicesManager, +}) { + const { CineService, CornerstoneViewportService } = servicesManager.services; + + const onImageScrollbarChange = (imageIndex, viewportIndex) => { + const viewportInfo = CornerstoneViewportService.getViewportInfoByIndex( + viewportIndex + ); + + const viewportId = viewportInfo.getViewportId(); + const viewport = CornerstoneViewportService.getCornerstoneViewport( + viewportId + ); + + // on image scrollbar change, stop the CINE if it is playing + CineService.stopClip(element); + CineService.setCine({ id: viewportIndex, isPlaying: false }); + + csToolsUtils.jumpToSlice(viewport.element, { + imageIndex, + debounceLoading: true, + }); + }; + + useEffect(() => { + if (!viewportData) { + return; + } + + const viewport = CornerstoneViewportService.getCornerstoneViewportByIndex( + viewportIndex + ); + + if (!viewport) { + return; + } + + if (viewportData.viewportType === Enums.ViewportType.STACK) { + const imageIndex = viewport.getCurrentImageIdIndex(); + + setImageSliceData({ + imageIndex: imageIndex, + numberOfSlices: viewportData.imageIds.length, + }); + + return; + } + + if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) { + const sliceData = utilities.getImageSliceDataForVolumeViewport( + viewport as Types.IVolumeViewport + ); + + if (!sliceData) { + return; + } + + const { imageIndex, numberOfSlices } = sliceData; + setImageSliceData({ imageIndex, numberOfSlices }); + } + }, [viewportIndex, viewportData]); + + useEffect(() => { + if (viewportData?.viewportType !== Enums.ViewportType.STACK) { + return; + } + + const updateStackIndex = event => { + const { newImageIdIndex } = event.detail; + // find the index of imageId in the imageIds + setImageSliceData({ + imageIndex: newImageIdIndex, + numberOfSlices: viewportData.imageIds.length, + }); + }; + + element.addEventListener( + Enums.Events.STACK_VIEWPORT_SCROLL, + updateStackIndex + ); + + return () => { + element.removeEventListener( + Enums.Events.STACK_VIEWPORT_SCROLL, + updateStackIndex + ); + }; + }, [viewportData, element]); + + useEffect(() => { + if (viewportData?.viewportType !== Enums.ViewportType.ORTHOGRAPHIC) { + return; + } + + const updateVolumeIndex = event => { + const { imageIndex, numberOfSlices } = event.detail; + // find the index of imageId in the imageIds + setImageSliceData({ imageIndex, numberOfSlices }); + }; + + element.addEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex); + + return () => { + element.removeEventListener( + Enums.Events.VOLUME_NEW_IMAGE, + updateVolumeIndex + ); + }; + }, [viewportData, element]); + + return ( + onImageScrollbarChange(evt, viewportIndex)} + max={ + imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0 + } + height={scrollbarHeight} + value={imageSliceData.imageIndex} + /> + ); +} + +CornerstoneImageScrollbar.propTypes = { + viewportData: PropTypes.object, + viewportIndex: PropTypes.number.isRequired, + element: PropTypes.instanceOf(Element), + scrollbarHeight: PropTypes.string, + imageSliceData: PropTypes.object.isRequired, + setImageSliceData: PropTypes.func.isRequired, +}; + +export default CornerstoneImageScrollbar; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportLoadingIndicator.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportLoadingIndicator.tsx new file mode 100644 index 00000000000..41efa66c094 --- /dev/null +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportLoadingIndicator.tsx @@ -0,0 +1,103 @@ +import React, { useEffect, useState, useRef } from 'react'; +import PropTypes from 'prop-types'; +import { Enums } from '@cornerstonejs/core'; + +function ViewportLoadingIndicator({ viewportData, element }) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + + const loadIndicatorRef = useRef(null); + const imageIdToBeLoaded = useRef(null); + + const setLoadingState = evt => { + clearTimeout(loadIndicatorRef.current); + + loadIndicatorRef.current = setTimeout(() => { + setLoading(true); + }, 50); + }; + + const setFinishLoadingState = evt => { + clearTimeout(loadIndicatorRef.current); + + setLoading(false); + }; + + const setErrorState = evt => { + clearTimeout(loadIndicatorRef.current); + + if (imageIdToBeLoaded.current === evt.detail.imageId) { + setError(evt.detail.error); + imageIdToBeLoaded.current = null; + } + }; + + useEffect(() => { + element.addEventListener( + Enums.Events.STACK_VIEWPORT_SCROLL, + setLoadingState + ); + element.addEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); + element.addEventListener( + Enums.Events.STACK_NEW_IMAGE, + setFinishLoadingState + ); + + return () => { + element.removeEventListener( + Enums.Events.STACK_VIEWPORT_SCROLL, + setLoadingState + ); + + element.removeEventListener( + Enums.Events.STACK_NEW_IMAGE, + setFinishLoadingState + ); + + element.removeEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); + }; + }, [element, viewportData]); + + if (error) { + return ( + <> +
+
+

+

Error Loading Image

+

An error has occurred.

+

{error}

+

+
+
+ + ); + } + + if (loading) { + return ( + // IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from + // interacting with the mouse, since scrolling should propagate to the viewport underneath +
+
+

Loading...

+
+
+ ); + } + + return null; +} + +ViewportLoadingIndicator.propTypes = { + percentComplete: PropTypes.number, + error: PropTypes.object, + element: PropTypes.object, +}; + +ViewportLoadingIndicator.defaultProps = { + percentComplete: 0, + error: null, +}; + +export default ViewportLoadingIndicator; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.css b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.css new file mode 100644 index 00000000000..c8d826d321f --- /dev/null +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.css @@ -0,0 +1,39 @@ +.ViewportOrientationMarkers { + --marker-width: 100px; + --marker-height: 100px; + --scrollbar-width: 20px; + pointer-events: none; + font-size: 15px; + color: #ccc; + line-height: 18px; +} +.ViewportOrientationMarkers .orientation-marker { + position: absolute; +} +.ViewportOrientationMarkers .top-mid { + top: 5px; + left: 50%; +} +.ViewportOrientationMarkers .left-mid { + top: 47%; + left: 5px; +} +.ViewportOrientationMarkers .right-mid { + top: 47%; + left: calc(100% - var(--marker-width) - var(--scrollbar-width)); +} +.ViewportOrientationMarkers .bottom-mid { + top: calc(100% - var(--marker-height) - 5px); + left: 47%; +} +.ViewportOrientationMarkers .right-mid .orientation-marker-value { + display: flex; + justify-content: flex-end; + min-width: var(--marker-width); +} +.ViewportOrientationMarkers .bottom-mid .orientation-marker-value { + display: flex; + justify-content: flex-start; + min-height: var(--marker-height); + flex-direction: column-reverse; +} diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx new file mode 100644 index 00000000000..6bea5975a6e --- /dev/null +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx @@ -0,0 +1,195 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { metaData, Enums, Types } from '@cornerstonejs/core'; +import { utilities } from '@cornerstonejs/tools'; + +import './ViewportOrientationMarkers.css'; +import { getEnabledElement } from '../../state'; + +/** + * + * Computes the orientation labels on a Cornerstone-enabled Viewport element + * when the viewport settings change (e.g. when a horizontal flip or a rotation occurs) + * + * @param {*} rowCosines + * @param {*} columnCosines + * @param {*} rotation in degrees + * @returns + */ +function getOrientationMarkers( + rowCosines, + columnCosines, + rotation, + flipVertical, + flipHorizontal +) { + const { + getOrientationStringLPS, + invertOrientationStringLPS, + } = utilities.orientation; + const rowString = getOrientationStringLPS(rowCosines); + const columnString = getOrientationStringLPS(columnCosines); + const oppositeRowString = invertOrientationStringLPS(rowString); + const oppositeColumnString = invertOrientationStringLPS(columnString); + + const markers = { + top: oppositeColumnString, + left: oppositeRowString, + right: rowString, + bottom: columnString, + }; + + // If any vertical or horizontal flips are applied, change the orientation strings ahead of + // the rotation applications + if (flipVertical) { + markers.top = invertOrientationStringLPS(markers.top); + markers.bottom = invertOrientationStringLPS(markers.bottom); + } + + if (flipHorizontal) { + markers.left = invertOrientationStringLPS(markers.left); + markers.right = invertOrientationStringLPS(markers.right); + } + + // Swap the labels accordingly if the viewport has been rotated + // This could be done in a more complex way for intermediate rotation values (e.g. 45 degrees) + if (rotation === 90 || rotation === -270) { + return { + top: markers.left, + left: invertOrientationStringLPS(markers.top), + right: invertOrientationStringLPS(markers.bottom), + bottom: markers.right, // left + }; + } else if (rotation === -90 || rotation === 270) { + return { + top: invertOrientationStringLPS(markers.left), + left: markers.top, + bottom: markers.left, + right: markers.bottom, + }; + } else if (rotation === 180 || rotation === -180) { + return { + top: invertOrientationStringLPS(markers.top), + left: invertOrientationStringLPS(markers.left), + bottom: invertOrientationStringLPS(markers.bottom), + right: invertOrientationStringLPS(markers.right), + }; + } + + return markers; +} + +function ViewportOrientationMarkers({ + element, + viewportData, + imageSliceData, + viewportIndex, + orientationMarkers = ['top', 'left'], +}) { + // Rotation is in degrees + const [rotation, setRotation] = useState(0); + const [flipHorizontal, setFlipHorizontal] = useState(false); + const [flipVertical, setFlipVertical] = useState(false); + + useEffect(() => { + const cameraModifiedListener = ( + evt: Types.EventTypes.CameraModifiedEvent + ) => { + + const { rotation, previousCamera, camera } = evt.detail; + + if (rotation !== undefined) { + setRotation(rotation); + } + + if (camera.flipHorizontal !== undefined && + previousCamera.flipHorizontal !== camera.flipHorizontal) { + setFlipHorizontal(camera.flipHorizontal); + } + + if (camera.flipVertical !== undefined && + previousCamera.flipVertical !== camera.flipVertical) { + setFlipVertical(camera.flipVertical); + } + }; + + element.addEventListener( + Enums.Events.CAMERA_MODIFIED, + cameraModifiedListener + ); + + return () => { + element.removeEventListener( + Enums.Events.CAMERA_MODIFIED, + cameraModifiedListener + ); + }; + }, []); + + const getMarkers = useCallback( + orientationMarkers => { + // Todo: support orientation markers for the volume viewports + if ( + !viewportData || + viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC + ) { + return ''; + } + + const imageIndex = imageSliceData.imageIndex; + const imageId = viewportData?.imageIds[imageIndex]; + + // Workaround for below TODO stub + if (!imageId) { + return false; + } + + const { rowCosines, columnCosines } = + metaData.get('imagePlaneModule', imageId) || {}; + + if (!rowCosines || !columnCosines || rotation === undefined) { + return false; + } + + if (!rowCosines || !columnCosines) { + return ''; + } + + const markers = getOrientationMarkers( + rowCosines, + columnCosines, + rotation, + flipVertical, + flipHorizontal + ); + + return orientationMarkers.map((m, index) => ( +
+
{markers[m]}
+
+ )); + }, + [flipHorizontal, flipVertical, rotation, viewportData, imageSliceData] + ); + + return ( +
+ {getMarkers(orientationMarkers)} +
+ ); +} + +ViewportOrientationMarkers.propTypes = { + percentComplete: PropTypes.number, + error: PropTypes.object, +}; + +ViewportOrientationMarkers.defaultProps = { + percentComplete: 0, + error: null, +}; + +export default ViewportOrientationMarkers; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportOverlay.tsx new file mode 100644 index 00000000000..29d8b863d5e --- /dev/null +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOverlay.tsx @@ -0,0 +1,273 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { vec3 } from 'gl-matrix'; +import PropTypes from 'prop-types'; +import { metaData, Enums, utilities } from '@cornerstonejs/core'; +import { ViewportOverlay } from '@ohif/ui'; + +const EPSILON = 1e-4; + +function CornerstoneViewportOverlay({ + element, + viewportData, + imageSliceData, + viewportIndex, + servicesManager, +}) { + const { + CornerstoneViewportService, + ToolBarService, + } = servicesManager.services; + const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null }); + const [scale, setScale] = useState(1); + const [activeTools, setActiveTools] = useState([]); + + /** + * Initial toolbar state + */ + useEffect(() => { + setActiveTools(ToolBarService.getActiveTools()); + }, []); + + /** + * Updating the VOI when the viewport changes its voi + */ + useEffect(() => { + const updateVOI = eventDetail => { + const { range } = eventDetail.detail; + + if (!range) { + return; + } + + const { lower, upper } = range; + const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel( + lower, + upper + ); + + setVOI({ windowCenter, windowWidth }); + }; + + element.addEventListener(Enums.Events.VOI_MODIFIED, updateVOI); + + return () => { + element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); + }; + }, [viewportIndex, viewportData, voi, element]); + + /** + * Updating the scale when the viewport changes its zoom + */ + useEffect(() => { + const updateScale = eventDetail => { + const { previousCamera, camera } = eventDetail.detail; + + if ( + previousCamera.parallelScale !== camera.parallelScale || + previousCamera.scale !== camera.scale + ) { + const viewport = CornerstoneViewportService.getCornerstoneViewportByIndex( + viewportIndex + ); + + if (!viewport) { + return; + } + + const imageData = viewport.getImageData(); + + if (!imageData) { + return; + } + + if (camera.scale) { + setScale(camera.scale); + return; + } + + const { spacing } = imageData; + // convert parallel scale to scale + const scale = + (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; + setScale(scale); + } + }; + + element.addEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); + + return () => { + element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); + }; + }, [viewportIndex, viewportData]); + + /** + * Updating the active tools when the toolbar changes + */ + // Todo: this should act on the toolGroups instead of the toolbar state + useEffect(() => { + const { unsubscribe } = ToolBarService.subscribe( + ToolBarService.EVENTS.TOOL_BAR_STATE_MODIFIED, + () => { + setActiveTools(ToolBarService.getActiveTools()); + } + ); + + return () => { + unsubscribe(); + }; + }, [ToolBarService]); + + const getTopLeftContent = useCallback(() => { + const { windowWidth, windowCenter } = voi; + + if (activeTools.includes('WindowLevel')) { + if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') { + return null; + } + + return ( +
+ W: + {windowWidth.toFixed(0)} + L: + {windowCenter.toFixed(0)} +
+ ); + } + + if (activeTools.includes('Zoom')) { + return ( +
+ Zoom: + {scale.toFixed(2)}x +
+ ); + } + + return null; + }, [voi, scale, activeTools]); + + const getTopRightContent = useCallback(() => { + const { imageIndex, numberOfSlices } = imageSliceData; + if (!viewportData) { + return; + } + + let instanceNumber; + + if (viewportData.viewportType === Enums.ViewportType.STACK) { + instanceNumber = _getInstanceNumberFromStack(viewportData, imageIndex); + + if (!instanceNumber) { + return null; + } + } else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) { + instanceNumber = _getInstanceNumberFromVolume( + viewportData, + imageIndex, + viewportIndex, + CornerstoneViewportService + ); + } + + return ( +
+ I: + + {instanceNumber !== undefined + ? `${instanceNumber} (${imageIndex + 1}/${numberOfSlices})` + : `${imageIndex + 1}/${numberOfSlices}`} + +
+ ); + }, [imageSliceData, viewportData, viewportIndex]); + + if (!viewportData) { + return null; + } + + if (viewportData.imageIds.length === 0) { + throw new Error( + 'ViewportOverlay: only viewports with imageIds is supported at this time' + ); + } + + return ( + + ); +} + +function _getInstanceNumberFromStack(viewportData, imageIndex) { + const imageIds = viewportData.imageIds; + const imageId = imageIds[imageIndex]; + + if (!imageId) { + return; + } + + const generalImageModule = metaData.get('generalImageModule', imageId) || {}; + const { instanceNumber } = generalImageModule; + + const stackSize = imageIds.length; + + if (stackSize <= 1) { + return; + } + + return parseInt(instanceNumber); +} + +// Since volume viewports can be in any view direction, they can render +// a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber +// Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber +function _getInstanceNumberFromVolume( + viewportData, + imageIndex, + viewportIndex, + CornerstoneViewportService +) { + const volumes = viewportData.volumes; + + // Todo: support fusion of acquisition plane which has instanceNumber + if (!volumes || volumes.length > 1) { + return; + } + + const volume = volumes[0]; + const { direction, imageIds } = volume; + + const cornerstoneViewport = CornerstoneViewportService.getCornerstoneViewportByIndex( + viewportIndex + ); + + if (!cornerstoneViewport) { + return; + } + + const camera = cornerstoneViewport.getCamera(); + const { viewPlaneNormal } = camera; + // checking if camera is looking at the acquisition plane (defined by the direction on the volume) + + const scanAxisNormal = direction.slice(6, 9); + + // check if viewPlaneNormal is parallel to scanAxisNormal + const cross = vec3.cross(vec3.create(), viewPlaneNormal, scanAxisNormal); + const isAcquisitionPlane = vec3.length(cross) < EPSILON; + + if (isAcquisitionPlane) { + const { instanceNumber } = + metaData.get('generalImageModule', imageIds[imageIndex]) || {}; + return parseInt(instanceNumber); + } +} + +CornerstoneViewportOverlay.propTypes = { + viewportData: PropTypes.object, + imageIndex: PropTypes.number, + viewportIndex: PropTypes.number, +}; + +export default CornerstoneViewportOverlay; diff --git a/extensions/cornerstone/src/ViewportLoadingIndicator.js b/extensions/cornerstone/src/ViewportLoadingIndicator.js deleted file mode 100644 index 2030d0d238c..00000000000 --- a/extensions/cornerstone/src/ViewportLoadingIndicator.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -import PropTypes from 'prop-types'; - -const ViewportLoadingIndicator = ({ error }) => { - if (error) { - return ( - <> -
-
-

Error Loading Image

-

An error has occurred.

-

{error.message}

-
- - ); - } - - return ( - <> -
-
-

Loading...

-
- - ); -}; - -ViewportLoadingIndicator.propTypes = { - percentComplete: PropTypes.number, - error: PropTypes.object, -}; - -ViewportLoadingIndicator.defaultProps = { - percentComplete: 0, - error: null, -}; - -export default ViewportLoadingIndicator; diff --git a/extensions/cornerstone/src/ViewportOverlay.js b/extensions/cornerstone/src/ViewportOverlay.js deleted file mode 100644 index b3f7e31dea7..00000000000 --- a/extensions/cornerstone/src/ViewportOverlay.js +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cornerstone from 'cornerstone-core'; -import classnames from 'classnames'; - -const ViewportOverlay = ({ - imageId, - scale, - windowWidth, - windowCenter, - imageIndex, - stackSize, - activeTools, -}) => { - const topLeft = 'top-viewport left-viewport'; - const topRight = 'top-viewport right-viewport-scrollbar'; - const bottomRight = 'bottom-viewport right-viewport-scrollbar'; - const bottomLeft = 'bottom-viewport left-viewport'; - const overlay = 'absolute pointer-events-none'; - - const isZoomActive = activeTools.includes('Zoom'); - const isWwwcActive = activeTools.includes('Wwwc'); - - if (!imageId) { - return null; - } - - // TODO: this component should be presentational only. Right now it has a weird dependency on Cornerstone - const generalImageModule = - cornerstone.metaData.get('generalImageModule', imageId) || {}; - const { instanceNumber } = generalImageModule; - - return ( -
-
- {isZoomActive && ( -
- Zoom: - {scale.toFixed(2)}x -
- )} - {isWwwcActive && ( -
- W: - - {windowWidth.toFixed(0)} - - L: - {windowCenter.toFixed(0)} -
- )} -
-
- {stackSize > 1 && ( -
- I: - - {`${instanceNumber} (${imageIndex}/${stackSize})`} - -
- )} -
-
-
-
- ); -}; - -ViewportOverlay.propTypes = { - scale: PropTypes.number.isRequired, - windowWidth: PropTypes.number.isRequired, - windowCenter: PropTypes.number.isRequired, - imageId: PropTypes.string.isRequired, - imageIndex: PropTypes.number.isRequired, - stackSize: PropTypes.number.isRequired, - activeTools: PropTypes.arrayOf(PropTypes.string), -}; - -ViewportOverlay.defaultProps = { - activeTools: [], -}; - -export default ViewportOverlay; diff --git a/extensions/cornerstone/src/_shared/getTools.js b/extensions/cornerstone/src/_shared/getTools.js deleted file mode 100644 index c3d9b9e7740..00000000000 --- a/extensions/cornerstone/src/_shared/getTools.js +++ /dev/null @@ -1,36 +0,0 @@ -import csTools from 'cornerstone-tools'; - -const toolsGroupedByType = { - touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool], - annotations: [ - csTools.ArrowAnnotateTool, - csTools.BidirectionalTool, - csTools.LengthTool, - csTools.AngleTool, - csTools.FreehandRoiTool, - csTools.EllipticalRoiTool, - csTools.DragProbeTool, - csTools.RectangleRoiTool, - ], - other: [ - csTools.PanTool, - csTools.ZoomTool, - csTools.WwwcTool, - csTools.WwwcRegionTool, - csTools.MagnifyTool, - csTools.StackScrollTool, - csTools.StackScrollMouseWheelTool, - csTools.OverlayTool, - ], -}; - -export default function getTools() { - const tools = []; - Object.keys(toolsGroupedByType).forEach(toolsGroup => - tools.push(...toolsGroupedByType[toolsGroup]) - ); - - return tools; -} - -export { toolsGroupedByType }; diff --git a/extensions/cornerstone/src/_shared/setActiveAndPassiveToolsForElement.js b/extensions/cornerstone/src/_shared/setActiveAndPassiveToolsForElement.js deleted file mode 100644 index b8348c99080..00000000000 --- a/extensions/cornerstone/src/_shared/setActiveAndPassiveToolsForElement.js +++ /dev/null @@ -1,21 +0,0 @@ -import csTools from 'cornerstone-tools'; - -export default function _setActiveAndPassiveToolsForElement(element, tools) { - const BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool'); - - tools.forEach(tool => { - if (tool.prototype instanceof BaseAnnotationTool) { - // BaseAnnotationTool would likely come from csTools lib exports - const toolName = new tool().name; - csTools.setToolPassiveForElement(element, toolName); // there may be a better place to determine name; may not be on uninstantiated class - } - }); - - csTools.setToolActiveForElement(element, 'Pan', { mouseButtonMask: 4 }); - csTools.setToolActiveForElement(element, 'Zoom', { mouseButtonMask: 2 }); - csTools.setToolActiveForElement(element, 'Wwwc', { mouseButtonMask: 1 }); - csTools.setToolActiveForElement(element, 'StackScrollMouseWheel', {}); // TODO: Empty options should not be required - csTools.setToolActiveForElement(element, 'PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options - csTools.setToolActiveForElement(element, 'ZoomTouchPinch', {}); - csTools.setToolEnabledForElement(element, 'Overlay', {}); -} diff --git a/extensions/cornerstone/src/_shared/setCornerstoneMeasurementActive.js b/extensions/cornerstone/src/_shared/setCornerstoneMeasurementActive.js deleted file mode 100644 index b9b689f0835..00000000000 --- a/extensions/cornerstone/src/_shared/setCornerstoneMeasurementActive.js +++ /dev/null @@ -1,39 +0,0 @@ -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; - -const { globalImageIdSpecificToolStateManager } = cornerstoneTools; - -export default function setCornerstoneMeasurementActive(measurement) { - const { id } = measurement; - - const toolState = globalImageIdSpecificToolStateManager.saveToolState(); - - Object.keys(toolState).forEach(imageId => { - const imageIdSpecificToolState = toolState[imageId]; - - Object.keys(imageIdSpecificToolState).forEach(toolType => { - const toolSpecificToolState = imageIdSpecificToolState[toolType]; - - const toolSpecificToolData = toolSpecificToolState.data; - - if (toolSpecificToolData && toolSpecificToolData.length) { - toolSpecificToolData.forEach(data => { - data.active = data.id === id ? true : false; - }); - } - }); - }); - - const enabledElements = cornerstoneTools.store.state.enabledElements; - - enabledElements.forEach(element => { - try { - cornerstone.updateImage(element); - } catch (ex) { - // https://github.com/cornerstonejs/cornerstone/blob/master/src/updateImage.js#L16 - // This fails if enabledElement.image is undefined and we have no layers - // Instead of throwing, it should _probably_ do nothing. - // We'll just swallow the exception - } - }); -} diff --git a/extensions/cornerstone/src/commandsModule.js b/extensions/cornerstone/src/commandsModule.js index 6759b0a6b01..e0953bafac8 100644 --- a/extensions/cornerstone/src/commandsModule.js +++ b/extensions/cornerstone/src/commandsModule.js @@ -1,406 +1,425 @@ -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; -import OHIF from '@ohif/core'; +import { + getEnabledElement, + StackViewport, + volumeLoader, + cache, + utilities as csUtils, +} from '@cornerstonejs/core'; +import { + ToolGroupManager, + Enums, + segmentation, + utilities as csToolsUtils, +} from '@cornerstonejs/tools'; +import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm'; + +import { getEnabledElement as OHIFgetEnabledElement } from './state'; +import callInputDialog from './utils/callInputDialog'; +import { setColormap } from './utils/colormap/transferFunctionHelpers'; + +const commandsModule = ({ servicesManager }) => { + const { + ViewportGridService, + ToolGroupService, + CineService, + ToolBarService, + UIDialogService, + CornerstoneViewportService, + SegmentationService, + } = servicesManager.services; + + function _getActiveViewportEnabledElement() { + const { activeViewportIndex } = ViewportGridService.getState(); + const { element } = OHIFgetEnabledElement(activeViewportIndex) || {}; + const enabledElement = getEnabledElement(element); + return enabledElement; + } -//import setCornerstoneLayout from './utils/setCornerstoneLayout.js'; -import { getEnabledElement } from './state'; -import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm'; -const scroll = cornerstoneTools.import('util/scroll'); + function _getToolGroup(toolGroupId) { + let toolGroupIdToUse = toolGroupId; -const { studyMetadataManager } = OHIF.utils; + if (!toolGroupIdToUse) { + // Use the active viewport's tool group if no tool group id is provided + const enabledElement = _getActiveViewportEnabledElement(); -const imagePositionSynchronizer = new cornerstoneTools.Synchronizer( - cornerstone.EVENTS.NEW_IMAGE, - cornerstoneTools.stackImagePositionSynchronizer -); + if (!enabledElement) { + return; + } -const panZoomSynchronizer = new cornerstoneTools.Synchronizer( - cornerstone.EVENTS.IMAGE_RENDERED, - cornerstoneTools.panZoomSynchronizer -); + const { renderingEngineId, viewportId } = enabledElement; + const toolGroup = ToolGroupManager.getToolGroupForViewport( + viewportId, + renderingEngineId + ); + + if (!toolGroup) { + console.warn( + 'No tool group found for viewportId:', + viewportId, + 'and renderingEngineId:', + renderingEngineId + ); + return; + } -function onElementEnabledAddToSync(event) { - const { element } = event.detail; + toolGroupIdToUse = toolGroup.id; + } - imagePositionSynchronizer.add(element); - // panZoomSynchronizer.add(element); -} + const toolGroup = ToolGroupService.getToolGroup(toolGroupIdToUse); + return toolGroup; + } -function onElementDisabledRemoveFromSync(event) { - const { element } = event.detail; + const actions = { + getActiveViewportEnabledElement: () => { + return _getActiveViewportEnabledElement(); + }, + setViewportActive: ({ viewportId }) => { + const viewportInfo = CornerstoneViewportService.getViewportInfo( + viewportId + ); + if (!viewportInfo) { + console.warn('No viewport found for viewportId:', viewportId); + return; + } - imagePositionSynchronizer.remove(element); - panZoomSynchronizer.remove(element); -} + const viewportIndex = viewportInfo.getViewportIndex(); + ViewportGridService.setActiveViewportIndex(viewportIndex); + }, + arrowTextCallback: ({ callback, data }) => { + callInputDialog(UIDialogService, data, callback); + }, + toggleCine: () => { + const { viewports } = ViewportGridService.getState(); + const { isCineEnabled } = CineService.getState(); + CineService.setIsCineEnabled(!isCineEnabled); + ToolBarService.setButton('Cine', { props: { isActive: !isCineEnabled } }); + viewports.forEach((_, index) => + CineService.setCine({ id: index, isPlaying: false }) + ); + }, + setWindowLevel({ window, level, toolGroupId }) { + // convert to numbers + const windowWidthNum = Number(window); + const windowCenterNum = Number(level); -const commandsModule = ({ servicesManager, commandsManager }) => { - const { ViewportGridService } = servicesManager.services; + const { viewportId } = _getActiveViewportEnabledElement(); + const viewportToolGroupId = ToolGroupService.getToolGroupForViewport( + viewportId + ); - function _getActiveViewportsEnabledElement() { - const { activeViewportIndex } = ViewportGridService.getState(); - const { element } = getEnabledElement(activeViewportIndex) || {}; - return element; - } + if (toolGroupId && toolGroupId !== viewportToolGroupId) { + return; + } - const actions = { - getCornerstoneLibraries: () => { - return { cornerstone, cornerstoneTools }; - }, - rotateViewport: ({ rotation }) => { - const enabledElement = _getActiveViewportsEnabledElement(); + // get actor from the viewport + const renderingEngine = CornerstoneViewportService.getRenderingEngine(); + const viewport = renderingEngine.getViewport(viewportId); + + const lower = windowCenterNum - windowWidthNum / 2.0; + const upper = windowCenterNum + windowWidthNum / 2.0; + + if (viewport instanceof StackViewport) { + viewport.setProperties({ + voiRange: { + upper, + lower, + }, + }); - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.rotation += rotation; - cornerstone.setViewport(enabledElement, viewport); + viewport.render(); } }, - flipViewportHorizontal: () => { - const enabledElement = _getActiveViewportsEnabledElement(); + toggleCrosshairs({ toolGroupId, toggledState }) { + const toolName = 'Crosshairs'; + // If it is Enabled + if (toggledState) { + actions.setToolActive({ toolName, toolGroupId }); + return; + } + const toolGroup = _getToolGroup(toolGroupId); - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.hflip = !viewport.hflip; - cornerstone.setViewport(enabledElement, viewport); + if (!toolGroup) { + return; } - }, - flipViewportVertical: () => { - const enabledElement = _getActiveViewportsEnabledElement(); - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.vflip = !viewport.vflip; - cornerstone.setViewport(enabledElement, viewport); + toolGroup.setToolDisabled(toolName); + + // Get the primary toolId from the ToolBarService and set it to active + // Since it was set to passive if not already active + const primaryActiveTool = ToolBarService.state.primaryToolId; + if ( + toolGroup?.toolOptions[primaryActiveTool]?.mode === + Enums.ToolModes.Passive + ) { + toolGroup.setToolActive(primaryActiveTool, { + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }); } }, - scaleViewport: ({ direction }) => { - const enabledElement = _getActiveViewportsEnabledElement(); - const step = direction * 0.15; - - if (enabledElement) { - if (step) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.scale += step; - cornerstone.setViewport(enabledElement, viewport); - } else { - cornerstone.fitToWindow(enabledElement); + setToolActive: ({ toolName, toolGroupId = null }) => { + const toolGroup = _getToolGroup(toolGroupId); + + if (!toolGroup) { + console.warn('No tool group found for toolGroupId:', toolGroupId); + return; + } + // Todo: we need to check if the viewports of the toolGroup is actually + // parts of the ViewportGrid's viewports, if not we return + + const { viewports } = ViewportGridService.getState() || { + viewports: [], + }; + + // iterate over all viewports and set the tool active for the + // viewports that belong to the toolGroup + for (let index = 0; index < viewports.length; index++) { + const ohifEnabledElement = OHIFgetEnabledElement(index); + + if (!ohifEnabledElement) { + continue; + } + + const viewport = getEnabledElement(ohifEnabledElement.element); + + if (!viewport) { + continue; + } + + // Find the current active tool and set it to be passive + const activeTool = toolGroup.getActivePrimaryMouseButtonTool(); + + if (activeTool) { + toolGroup.setToolPassive(activeTool); } + + // Set the new toolName to be active + toolGroup.setToolActive(toolName, { + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }); + + return; } }, - resetViewport: () => { - const enabledElement = _getActiveViewportsEnabledElement(); + showDownloadViewportModal: () => { + const { activeViewportIndex } = ViewportGridService.getState(); + const { UIModalService } = servicesManager.services; - if (enabledElement) { - cornerstone.reset(enabledElement); + if (UIModalService) { + UIModalService.show({ + content: CornerstoneViewportDownloadForm, + title: 'Download High Quality Image', + contentProps: { + activeViewportIndex, + onClose: UIModalService.hide, + CornerstoneViewportService, + }, + }); } }, - toggleSynchronizer: ({ toggledState }) => { - const synchronizers = [imagePositionSynchronizer]; - // Set synchronizer state when the command is run. - synchronizers.forEach(s => { - s.enabled = toggledState; - }); + rotateViewport: ({ rotation }) => { + const enabledElement = _getActiveViewportEnabledElement(); + if (!enabledElement) { + return; + } - const unsubscribe = () => { - cornerstone.events.removeEventListener( - cornerstone.EVENTS.ELEMENT_ENABLED, - onElementEnabledAddToSync - ); - cornerstone.events.removeEventListener( - cornerstone.EVENTS.ELEMENT_DISABLED, - onElementDisabledRemoveFromSync - ); - }; - const subscribe = () => { - cornerstone.events.addEventListener( - cornerstone.EVENTS.ELEMENT_ENABLED, - onElementEnabledAddToSync - ); - cornerstone.events.addEventListener( - cornerstone.EVENTS.ELEMENT_DISABLED, - onElementDisabledRemoveFromSync - ); - }; + const { viewport } = enabledElement; - // Add event handlers so that if the layout is changed, new elements - // are automatically added to the synchronizer while it is enabled. - if (toggledState === true) { - subscribe(); - } else { - // If the synchronizer is disabled, remove the event handlers - unsubscribe(); + if (viewport instanceof StackViewport) { + const { rotation: currentRotation } = viewport.getProperties(); + const newRotation = (currentRotation + rotation) % 360; + viewport.setProperties({ rotation: newRotation }); + viewport.render(); } + }, + flipViewportHorizontal: () => { + const enabledElement = _getActiveViewportEnabledElement(); - // Erase existing state and then set up all currently existing elements - cornerstone.getEnabledElements().map(e => { - synchronizers.forEach(s => { - s.remove(e.element); - s.add(e.element); - }); - }); - return unsubscribe; + if (!enabledElement) { + return; + } + + const { viewport } = enabledElement; + + if (viewport instanceof StackViewport) { + const { flipHorizontal } = viewport.getCamera(); + viewport.setCamera({ flipHorizontal: !flipHorizontal }); + viewport.render(); + } + }, + flipViewportVertical: () => { + const enabledElement = _getActiveViewportEnabledElement(); + + if (!enabledElement) { + return; + } + + const { viewport } = enabledElement; + + if (viewport instanceof StackViewport) { + const { flipVertical } = viewport.getCamera(); + viewport.setCamera({ flipVertical: !flipVertical }); + viewport.render(); + } }, invertViewport: ({ element }) => { let enabledElement; if (element === undefined) { - enabledElement = _getActiveViewportsEnabledElement(); + enabledElement = _getActiveViewportEnabledElement(); } else { enabledElement = element; } - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); - viewport.invert = !viewport.invert; - cornerstone.setViewport(enabledElement, viewport); + if (!enabledElement) { + return; } - }, - cancelMeasurement: () => { - const enabledElement = _getActiveViewportsEnabledElement(); - - if (enabledElement) { - const cancelActiveManipulatorsForElement = cornerstoneTools.getModule( - 'manipulatorState' - ).setters.cancelActiveManipulatorsForElement; - cancelActiveManipulatorsForElement(enabledElement); + const { viewport } = enabledElement; - cornerstone.updateImage(enabledElement); + if (viewport instanceof StackViewport) { + const { invert } = viewport.getProperties(); + viewport.setProperties({ invert: !invert }); + viewport.render(); } }, - // TODO: this is receiving `evt` from `ToolbarRow`. We could use it to have - // better mouseButtonMask sets. - setToolActive: ({ toolName }) => { - if (!toolName) { - console.warn('No toolname provided to setToolActive command'); + resetViewport: () => { + const enabledElement = _getActiveViewportEnabledElement(); + + if (!enabledElement) { + return; } - // Find total number of tool indexes - const { viewports } = ViewportGridService.getState() || { viewports: [] }; - for (let i = 0; i < viewports.length; i++) { - const viewport = viewports[i]; - const hasDisplaySet = viewport.displaySetInstanceUID !== undefined; + const { viewport } = enabledElement; - if (!hasDisplaySet) { - continue; - } - - const viewportInfo = getEnabledElement(i); - if (!viewportInfo) continue; - const hasCornerstoneContext = - viewportInfo.context === 'ACTIVE_VIEWPORT::CORNERSTONE'; - - if (hasCornerstoneContext) { - cornerstoneTools.setToolActiveForElement( - viewportInfo.element, - toolName, - { mouseButtonMask: 1 } - ); - } else { - commandsManager.runCommand( - 'setToolActive', - { - element: viewportInfo.element, - toolName, - }, - viewportInfo.context - ); - } + if (viewport instanceof StackViewport) { + viewport.resetProperties(); + viewport.resetCamera(); + viewport.render(); } }, - clearAnnotations: () => { - const element = _getActiveViewportsEnabledElement(); - if (!element) { + scaleViewport: ({ direction }) => { + const enabledElement = _getActiveViewportEnabledElement(); + const scaleFactor = direction > 0 ? 0.9 : 1.1; + + if (!enabledElement) { return; } + const { viewport } = enabledElement; - const { enabledElement } = cornerstone.getEnabledElement(element) || {}; - if (!enabledElement || !enabledElement.image) { - return; + if (viewport instanceof StackViewport) { + if (direction) { + const { parallelScale } = viewport.getCamera(); + viewport.setCamera({ parallelScale: parallelScale * scaleFactor }); + viewport.render(); + } else { + viewport.resetCamera(); + viewport.render(); + } } + }, + scroll: ({ direction }) => { + const enabledElement = _getActiveViewportEnabledElement(); - const { - toolState, - } = cornerstoneTools.globalImageIdSpecificToolStateManager; - if ( - !toolState || - toolState.hasOwnProperty(enabledElement.image.imageId) === false - ) { + if (!enabledElement) { return; } - const imageIdToolState = toolState[enabledElement.image.imageId]; + const { viewport } = enabledElement; + const options = { delta: direction }; - const measurementsToRemove = []; - - Object.keys(imageIdToolState).forEach(toolType => { - const { data } = imageIdToolState[toolType]; - - data.forEach(measurementData => { - const { - _id, - lesionNamingNumber, - measurementNumber, - } = measurementData; + csToolsUtils.scroll(viewport, options); + }, + async createSegmentationForDisplaySet({ displaySetInstanceUID }) { + const volumeId = displaySetInstanceUID; - if (!_id) { - return; - } + const segmentationUID = csUtils.uuidv4(); + const segmentationId = `${volumeId}::${segmentationUID}`; - measurementsToRemove.push({ - toolType, - _id, - lesionNamingNumber, - measurementNumber, - }); - }); + await volumeLoader.createAndCacheDerivedVolume(volumeId, { + volumeId: segmentationId, }); - measurementsToRemove.forEach(measurementData => { - OHIF.measurements.MeasurementHandlers.onRemoved({ - detail: { - toolType: measurementData.toolType, - measurementData, - }, - }); - }); - }, - nextImage: () => { - const enabledElement = _getActiveViewportsEnabledElement(); - scroll(enabledElement, 1); - }, - previousImage: () => { - const enabledElement = _getActiveViewportsEnabledElement(); - scroll(enabledElement, -1); - }, - getActiveViewportEnabledElement: () => { - const enabledElement = _getActiveViewportsEnabledElement(); - return enabledElement; - }, - showDownloadViewportModal: () => { - const { activeViewportIndex } = ViewportGridService.getState(); - const { UIModalService } = servicesManager.services; - - if (UIModalService) { - UIModalService.show({ - content: CornerstoneViewportDownloadForm, - title: 'Download High Quality Image', - contentProps: { - activeViewportIndex, - onClose: UIModalService.hide, + // Add the segmentations to state + segmentation.addSegmentations([ + { + segmentationId, + representation: { + // The type of segmentation + type: Enums.SegmentationRepresentations.Labelmap, + // The actual segmentation data, in the case of labelmap this is a + // reference to the source volume of the segmentation. + data: { + volumeId: segmentationId, + }, }, - }); + }, + ]); + + return segmentationId; + }, + async addSegmentationRepresentationToToolGroup({ + segmentationId, + toolGroupId, + representationType, + }) { + // // Add the segmentation representation to the toolgroup + await segmentation.addSegmentationRepresentations(toolGroupId, [ + { + segmentationId, + type: representationType, + }, + ]); + }, + getLabelmapVolumes: ({ segmentations }) => { + if (!segmentations || !segmentations.length) { + segmentations = SegmentationService.getSegmentations(); } - }, - getNearbyToolData({ element, canvasCoordinates, availableToolTypes }) { - const nearbyTool = {}; - let pointNearTool = false; - availableToolTypes.forEach(toolType => { - const elementToolData = cornerstoneTools.getToolState( - element, - toolType - ); + const labelmapVolumes = segmentations.map(segmentation => { + return cache.getVolume(segmentation.id); + }); - if (!elementToolData) { - return; - } + return labelmapVolumes; + }, + setViewportColormap: ({ + viewportIndex, + displaySetInstanceUID, + colormap, + immediate = false, + }) => { + const viewport = CornerstoneViewportService.getCornerstoneViewportByIndex( + viewportIndex + ); - elementToolData.data.forEach((toolData, index) => { - let elementToolInstance = cornerstoneTools.getToolForElement( - element, - toolType - ); - - if (!elementToolInstance) { - elementToolInstance = cornerstoneTools.getToolForElement( - element, - `${toolType}Tool` - ); - } - - if (!elementToolInstance) { - console.warn('Tool not found.'); - return undefined; - } - - if ( - elementToolInstance.pointNearTool( - element, - toolData, - canvasCoordinates - ) - ) { - pointNearTool = true; - nearbyTool.tool = toolData; - nearbyTool.index = index; - nearbyTool.toolType = toolType; - } - }); + const actorEntries = viewport.getActors(); - if (pointNearTool) { - return false; - } + const actorEntry = actorEntries.find(actorEntry => { + return actorEntry.uid === displaySetInstanceUID; }); - return pointNearTool ? nearbyTool : undefined; - }, - removeToolState: ({ element, toolType, tool }) => { - cornerstoneTools.removeToolState(element, toolType, tool); - cornerstone.updateImage(element); - }, - // setCornerstoneLayout: () => { - // setCornerstoneLayout(); - // }, - setWindowLevel: ({ window, level }) => { - const enabledElement = _getActiveViewportsEnabledElement(); + const { actor: volumeActor } = actorEntry; - if (enabledElement) { - let viewport = cornerstone.getViewport(enabledElement); + setColormap(volumeActor, colormap); - viewport.voi = { - windowWidth: Number(window), - windowCenter: Number(level), - }; - cornerstone.setViewport(enabledElement, viewport); + if (immediate) { + viewport.render(); } }, }; const definitions = { - jumpToImage: { - commandFn: actions.jumpToImage, - storeContexts: [], - options: {}, - }, - getCornerstoneLibraries: { - commandFn: actions.getCornerstoneLibraries, - storeContexts: [], - options: {}, - context: 'VIEWER', - }, - getNearbyToolData: { - commandFn: actions.getNearbyToolData, - storeContexts: [], - options: {}, - }, - toggleSynchronizer: { - commandFn: actions.toggleSynchronizer, - storeContexts: [], - options: {}, - }, - removeToolState: { - commandFn: actions.removeToolState, + setWindowLevel: { + commandFn: actions.setWindowLevel, storeContexts: [], options: {}, }, - showDownloadViewportModal: { - commandFn: actions.showDownloadViewportModal, + setToolActive: { + commandFn: actions.setToolActive, storeContexts: [], options: {}, }, - getActiveViewportEnabledElement: { - commandFn: actions.getActiveViewportEnabledElement, + toggleCrosshairs: { + commandFn: actions.toggleCrosshairs, storeContexts: [], options: {}, }, @@ -414,23 +433,23 @@ const commandsModule = ({ servicesManager, commandsManager }) => { storeContexts: [], options: { rotation: -90 }, }, - invertViewport: { - commandFn: actions.invertViewport, + flipViewportHorizontal: { + commandFn: actions.flipViewportHorizontal, storeContexts: [], options: {}, }, - cancelMeasurement: { - commandFn: actions.cancelMeasurement, + flipViewportVertical: { + commandFn: actions.flipViewportVertical, storeContexts: [], options: {}, }, - flipViewportVertical: { - commandFn: actions.flipViewportVertical, + invertViewport: { + commandFn: actions.invertViewport, storeContexts: [], options: {}, }, - flipViewportHorizontal: { - commandFn: actions.flipViewportHorizontal, + resetViewport: { + commandFn: actions.resetViewport, storeContexts: [], options: {}, }, @@ -449,45 +468,54 @@ const commandsModule = ({ servicesManager, commandsManager }) => { storeContexts: [], options: { direction: 0 }, }, - resetViewport: { - commandFn: actions.resetViewport, + nextImage: { + commandFn: actions.scroll, + storeContexts: [], + options: { direction: 1 }, + }, + previousImage: { + commandFn: actions.scroll, + storeContexts: [], + options: { direction: -1 }, + }, + showDownloadViewportModal: { + commandFn: actions.showDownloadViewportModal, storeContexts: [], options: {}, }, - clearAnnotations: { - commandFn: actions.clearAnnotations, + toggleCine: { + commandFn: actions.toggleCine, storeContexts: [], options: {}, }, - nextImage: { - commandFn: actions.nextImage, + arrowTextCallback: { + commandFn: actions.arrowTextCallback, storeContexts: [], options: {}, }, - previousImage: { - commandFn: actions.previousImage, + setViewportActive: { + commandFn: actions.setViewportActive, storeContexts: [], options: {}, }, - // TOOLS - setToolActive: { - commandFn: actions.setToolActive, + createSegmentationForDisplaySet: { + commandFn: actions.createSegmentationForDisplaySet, storeContexts: [], options: {}, }, - setZoomTool: { - commandFn: actions.setToolActive, + addSegmentationRepresentationToToolGroup: { + commandFn: actions.addSegmentationRepresentationToToolGroup, storeContexts: [], - options: { toolName: 'Zoom' }, - }, - // setCornerstoneLayout: { - // commandFn: actions.setCornerstoneLayout, - // storeContexts: [], - // options: {}, - // context: 'VIEWER', - // }, - setWindowLevel: { - commandFn: actions.setWindowLevel, + options: {}, + }, + + getLabelmapVolumes: { + commandFn: actions.getLabelmapVolumes, + storeContexts: [], + options: {}, + }, + setViewportColormap: { + commandFn: actions.setViewportColormap, storeContexts: [], options: {}, }, @@ -496,7 +524,7 @@ const commandsModule = ({ servicesManager, commandsManager }) => { return { actions, definitions, - defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE', + defaultContext: 'CORNERSTONE', }; }; diff --git a/extensions/cornerstone/src/index.js b/extensions/cornerstone/src/index.js deleted file mode 100644 index 58b0e181dde..00000000000 --- a/extensions/cornerstone/src/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import init from './init.js'; -import commandsModule from './commandsModule.js'; -import { id } from './id.js'; -// import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm'; - -const Component = React.lazy(() => { - return import(/* webpackPrefetch: true */ './OHIFCornerstoneViewport'); -}); - -const OHIFCornerstoneViewport = props => { - return ( - Loading...}> - - - ); -}; - -/** - * - */ -export default { - /** - * Only required property. Should be a unique value across all extensions. - */ - id, - /** - * - * - * @param {object} [configuration={}] - * @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools` - */ - preRegistration({ servicesManager, commandsManager, configuration = {} }) { - init({ servicesManager, commandsManager, configuration }); - }, - getViewportModule({ servicesManager, commandsManager }) { - const ExtendedOHIFCornerstoneViewport = props => { - const onNewImageHandler = jumpData => { - commandsManager.runCommand('jumpToImage', jumpData); - }; - const { ToolBarService } = servicesManager.services; - - return ( - - ); - }; - - return [ - { name: 'cornerstone', component: ExtendedOHIFCornerstoneViewport }, - ]; - }, - getCommandsModule({ servicesManager, commandsManager }) { - return commandsModule({ servicesManager, commandsManager }); - }, -}; diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx new file mode 100644 index 00000000000..3e83998df33 --- /dev/null +++ b/extensions/cornerstone/src/index.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import * as cornerstone from '@cornerstonejs/core'; +import * as cornerstoneTools from '@cornerstonejs/tools'; +import { + Enums as cs3DEnums, + CONSTANTS, + imageLoadPoolManager, + imageRetrievalPoolManager, +} from '@cornerstonejs/core'; +import { Enums as cs3DToolsEnums } from '@cornerstonejs/tools'; +import init from './init.js'; +import commandsModule from './commandsModule'; +import ToolGroupService from './services/ToolGroupService'; +import SyncGroupService from './services/SyncGroupService'; +import { toolNames } from './initCornerstoneTools'; +import { getEnabledElement } from './state'; +import CornerstoneViewportService from './services/ViewportService/CornerstoneViewportService'; +import dicomLoaderService from './utils/dicomLoaderService'; +import { registerColormap } from './utils/colormap/transferFunctionHelpers'; + +import { id } from './id'; + +const Component = React.lazy(() => { + return import( + /* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport' + ); +}); + +const OHIFCornerstoneViewport = props => { + return ( + Loading...}> + + + ); +}; + +/** + * + */ +const cornerstoneExtension = { + /** + * Only required property. Should be a unique value across all extensions. + */ + id, + + onModeExit: () => { + // Empty out the image load and retrieval pools to prevent memory leaks + // on the mode exits + Object.values(cs3DEnums.RequestType).forEach(type => { + imageLoadPoolManager.clearRequestStack(type); + imageRetrievalPoolManager.clearRequestStack(type); + }); + }, + + /** + * + * + * @param {object} [configuration={}] + * @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools` + */ + async preRegistration({ + servicesManager, + commandsManager, + configuration = {}, + appConfig, + }) { + servicesManager.registerService( + CornerstoneViewportService(servicesManager) + ); + servicesManager.registerService(ToolGroupService(servicesManager)); + servicesManager.registerService(SyncGroupService(servicesManager)); + await init({ servicesManager, commandsManager, configuration, appConfig }); + }, + getViewportModule({ servicesManager, commandsManager }) { + const ExtendedOHIFCornerstoneViewport = props => { + // const onNewImageHandler = jumpData => { + // commandsManager.runCommand('jumpToImage', jumpData); + // }; + const { ToolBarService } = servicesManager.services; + + return ( + + ); + }; + + return [ + { + name: 'cornerstone', + component: ExtendedOHIFCornerstoneViewport, + }, + ]; + }, + getCommandsModule({ servicesManager, commandsManager, extensionManager }) { + return commandsModule({ + servicesManager, + commandsManager, + extensionManager, + }); + }, + getUtilityModule({ servicesManager }) { + return [ + { + name: 'common', + exports: { + getCornerstoneLibraries: () => { + return { cornerstone, cornerstoneTools }; + }, + getEnabledElement, + dicomLoaderService, + registerColormap, + }, + }, + { + name: 'core', + exports: { + Enums: cs3DEnums, + CONSTANTS, + }, + }, + { + name: 'tools', + exports: { + toolNames, + Enums: cs3DToolsEnums, + }, + }, + ]; + }, +}; + +export default cornerstoneExtension; diff --git a/extensions/cornerstone/src/init.js b/extensions/cornerstone/src/init.js index 01171e71cd6..a2a3173c7af 100644 --- a/extensions/cornerstone/src/init.js +++ b/extensions/cornerstone/src/init.js @@ -1,86 +1,126 @@ import OHIF from '@ohif/core'; import { ContextMenuMeasurements } from '@ohif/ui'; -import cs from 'cornerstone-core'; -import csTools from 'cornerstone-tools'; -import merge from 'lodash.merge'; -import getTools, { toolsGroupedByType } from './utils/getTools.js'; -import initCornerstoneTools from './initCornerstoneTools.js'; -import initWADOImageLoader from './initWADOImageLoader.js'; -import getCornerstoneMeasurementById from './utils/getCornerstoneMeasurementById'; -import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory'; -import { setEnabledElement } from './state'; -import callInputDialog from './callInputDialog.js'; - -// TODO -> Global "context menu open state", or lots of expensive searches on drag? + +import * as cornerstone from '@cornerstonejs/core'; +import * as cornerstoneTools from '@cornerstonejs/tools'; +import { + init as cs3DInit, + eventTarget, + EVENTS, + metaData, + volumeLoader, + imageLoader, + imageLoadPoolManager, + Settings, +} from '@cornerstonejs/core'; +import { Enums, utilities } from '@cornerstonejs/tools'; +import { + cornerstoneStreamingImageVolumeLoader, + sharedArrayBufferImageLoader, +} from '@cornerstonejs/streaming-image-volume-loader'; + +import initWADOImageLoader from './initWADOImageLoader'; +import initCornerstoneTools from './initCornerstoneTools'; + +import { connectToolsToMeasurementService } from './initMeasurementService'; +import callInputDialog from './utils/callInputDialog'; +import initCineService from './initCineService'; +import interleaveCenterLoader from './utils/interleaveCenterLoader'; +import interleaveTopToBottom from './utils/interleaveTopToBottom'; +import initSegmentationService from './initSegmentationService'; + +const cs3DToolsEvents = Enums.Events; let CONTEXT_MENU_OPEN = false; -const { globalImageIdSpecificToolStateManager } = csTools; - -const TOOL_TYPES_WITH_CONTEXT_MENU = [ - 'Angle', - 'ArrowAnnotate', - 'Bidirectional', - 'Length', - 'FreehandMouse', - 'EllipticalRoi', - 'CircleRoi', - 'RectangleRoi', - // SR Viewport... - 'SRLength', - 'SRBidirectional', - 'SRArrowAnnotate', - 'SREllipticalRoi', -]; - -const _refreshViewports = () => - cs.getEnabledElements().forEach(({ element }) => cs.updateImage(element)); - -/* Add extension tools configuration here. */ -const _createInternalToolsConfig = UIDialogService => { - return { - ArrowAnnotate: { - configuration: { - getTextCallback: (callback, eventDetails) => - callInputDialog(UIDialogService, null, callback), - changeTextCallback: (data, eventDetails, callback) => - callInputDialog(UIDialogService, data, callback), - allowEmptyLabel: true, - }, - }, - DragProbe: { - defaultStrategy: 'minimal', - }, - }; -}; +// TODO: Cypress tests are currently grabbing this from the window? +window.cornerstone = cornerstone; +window.cornerstoneTools = cornerstoneTools; /** * - * @param {Object} servicesManager - * @param {Object} configuration - * @param {Object|Array} configuration.csToolsConfig */ -export default function init({ +export default async function init({ servicesManager, commandsManager, configuration, + appConfig, }) { + await cs3DInit(); + + // For debugging e2e tests that are failing on CI + //cornerstone.setUseCPURendering(true); + + // For debugging large datasets + //cornerstone.cache.setMaxCacheSize(3000000000); + + initCornerstoneTools(); + + // Don't use cursors in viewports + // Todo: this should come from extension/app configuration + Settings.getRuntimeSettings().set('useCursors', false); + const { - UIDialogService, + UserAuthenticationService, + ToolGroupService, MeasurementService, DisplaySetService, - ToolBarService, - UserAuthenticationService, + UIDialogService, + CineService, + CornerstoneViewportService, + HangingProtocolService, + SegmentationService, } = servicesManager.services; - const tools = getTools(); - console.log(servicesManager.services); + const metadataProvider = OHIF.classes.MetadataProvider; + + volumeLoader.registerUnknownVolumeLoader( + cornerstoneStreamingImageVolumeLoader + ); + volumeLoader.registerVolumeLoader( + 'cornerstoneStreamingImageVolume', + cornerstoneStreamingImageVolumeLoader + ); + + HangingProtocolService.registerImageLoadStrategy( + 'interleaveCenter', + interleaveCenterLoader + ); + HangingProtocolService.registerImageLoadStrategy( + 'interleaveTopToBottom', + interleaveTopToBottom + ); + + imageLoader.registerImageLoader( + 'streaming-wadors', + sharedArrayBufferImageLoader + ); + + metaData.addProvider(metadataProvider.get.bind(metadataProvider), 9999); + + imageLoadPoolManager.maxNumRequests = { + interaction: appConfig?.maxNumRequests?.interaction || 100, + thumbnail: appConfig?.maxNumRequests?.thumbnail || 75, + prefetch: appConfig?.maxNumRequests?.prefetch || 10, + }; + + initWADOImageLoader(UserAuthenticationService, appConfig); /* Measurement Service */ - const measurementServiceSource = _connectToolsToMeasurementService( + const measurementServiceSource = connectToolsToMeasurementService( MeasurementService, - DisplaySetService + DisplaySetService, + CornerstoneViewportService ); + initSegmentationService(SegmentationService, CornerstoneViewportService); + + initCineService(CineService); + + const _getDefaultPosition = event => ({ + x: (event && event.currentPoints.client[0]) || 0, + y: (event && event.currentPoints.client[1]) || 0, + }); + const onRightClick = event => { if (!UIDialogService) { console.warn('Unable to show dialog; no UI Dialog Service available.'); @@ -89,16 +129,17 @@ export default function init({ const onGetMenuItems = defaultMenuItems => { const { element, currentPoints } = event.detail; - const nearbyToolData = commandsManager.runCommand('getNearbyToolData', { + + const nearbyToolData = utilities.getAnnotationNearPoint( element, - canvasCoordinates: currentPoints.canvas, - availableToolTypes: TOOL_TYPES_WITH_CONTEXT_MENU, - }); + currentPoints.canvas + ); let menuItems = []; if (nearbyToolData) { defaultMenuItems.forEach(item => { item.value = nearbyToolData; + item.element = element; menuItems.push(item); }); } @@ -123,41 +164,15 @@ export default function init({ onGetMenuItems, eventData: event.detail, onDelete: item => { - const { tool: measurementData, toolType } = item.value; + const { annotationUID } = item.value; + const uid = annotationUID; // Sync'd w/ Measurement Service - if (measurementData.id) { - measurementServiceSource.remove(measurementData.id); - } - // Only in cstools - else { - const toolState = - csTools.globalImageIdSpecificToolStateManager.toolState; - - Object.keys(toolState).forEach(imageId => { - Object.keys(toolState[imageId]).forEach(imageToolType => { - const numMeasurements = - toolState[imageId][imageToolType].data.length; - - for (let i = 0; i < numMeasurements; i++) { - const toolData = toolState[imageId][imageToolType].data[i]; - if (measurementData.uuid === toolData.uuid) { - // Clear and nuke matching measurement - toolState[imageId][imageToolType].data[i] = null; - delete toolState[imageId][imageToolType].data[i]; - toolState[imageId][imageToolType].data.splice(i, 1); - - // Delete "data" if we deleted last measurement in array - // if (toolState[imageId][imageToolType].data.length === 0) { - // delete toolState[imageId][imageToolType].data; - // } - } - } - }); + if (uid) { + measurementServiceSource.remove(uid, { + element: item.element, }); - console.log(csTools.store.state); } - _refreshViewports(); CONTEXT_MENU_OPEN = false; }, onClose: () => { @@ -165,11 +180,9 @@ export default function init({ UIDialogService.dismiss({ id: 'context-menu' }); }, onSetLabel: item => { - const { tool: measurementData } = item.value; + const { annotationUID } = item.value; - const measurement = MeasurementService.getMeasurement( - measurementData.id - ); + const measurement = MeasurementService.getMeasurement(annotationUID); callInputDialog( UIDialogService, @@ -184,7 +197,7 @@ export default function init({ }); MeasurementService.update( - updatedMeasurement.id, + updatedMeasurement.uid, updatedMeasurement, true ); @@ -198,19 +211,6 @@ export default function init({ }); }; - const onTouchPress = event => { - if (!UIDialogService) { - console.warn('Unable to show dialog; no UI Dialog Service available.'); - return; - } - - UIDialogService.create({ - eventData: event.detail, - content: ContextMenuMeasurements, - contentProps: { isTouchEvent: true }, - }); - }; - const resetContextMenu = () => { if (!UIDialogService) { console.warn('Unable to show dialog; no UI Dialog Service available.'); @@ -222,6 +222,24 @@ export default function init({ UIDialogService.dismiss({ id: 'context-menu' }); }; + // When a custom image load is performed, update the relevant viewports + HangingProtocolService.subscribe( + HangingProtocolService.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED, + volumeInputArrayMap => { + for (const entry of volumeInputArrayMap.entries()) { + const [viewportId, volumeInputArray] = entry; + const viewport = CornerstoneViewportService.getCornerstoneViewport( + viewportId + ); + + CornerstoneViewportService.setVolumesForViewport( + viewport, + volumeInputArray + ); + } + } + ); + /* * Because click gives us the native "mouse up", buttons will always be `0` * Need to fallback to event.which; @@ -235,445 +253,50 @@ export default function init({ clickMethodHandler(evt); }; - const cancelContextMenuIfOpen = evt => { - if (CONTEXT_MENU_OPEN) { - resetContextMenu(); - } - }; + // const cancelContextMenuIfOpen = evt => { + // if (CONTEXT_MENU_OPEN) { + // resetContextMenu(); + // } + // }; - // TODO: This is the handler for ALL ENABLED ELEMENT EVENTS - // ... Activation logic should take place per element, not for all (diff behavior per ext) - function elementEnabledHandler(tools, evt) { - const element = evt.detail.element; + function elementEnabledHandler(evt) { + const { element } = evt.detail; - _addConfiguredToolsForElement( - UIDialogService, - element, - tools, - configuration - ); - - element.addEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress); element.addEventListener( - csTools.EVENTS.MOUSE_CLICK, + cs3DToolsEvents.MOUSE_CLICK, contextMenuHandleClick ); - element.addEventListener(cs.EVENTS.NEW_IMAGE, cancelContextMenuIfOpen); + + eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, evt => { + const { element } = evt.detail; + utilities.stackPrefetch.enable(element); + }); } function elementDisabledHandler(evt) { - const element = evt.detail.element; - element.removeEventListener(csTools.EVENTS.TOUCH_PRESS, onTouchPress); + const { viewportId, element } = evt.detail; + + const viewportInfo = CornerstoneViewportService.getViewportInfo(viewportId); + ToolGroupService.disable(viewportInfo); + element.removeEventListener( - csTools.EVENTS.MOUSE_CLICK, + cs3DToolsEvents.MOUSE_CLICK, contextMenuHandleClick ); - element.removeEventListener(cs.EVENTS.NEW_IMAGE, cancelContextMenuIfOpen); - } - - const { csToolsConfig } = configuration; - const metadataProvider = OHIF.classes.MetadataProvider; - - cs.metaData.addProvider(metadataProvider.get.bind(metadataProvider), 9999); - - initWADOImageLoader(UserAuthenticationService); - - // ~~ - const defaultCsToolsConfig = csToolsConfig || { - globalToolSyncEnabled: false, // hold on to your pants! - showSVGCursors: false, - autoResizeViewports: false, - }; - - initCornerstoneTools(defaultCsToolsConfig); - // TODO: Extensions are still registered at time of registration globally - // These should be registered as a part of mode route spin up, - // and they need to self-clean on mode route destroy - // THIS - // is a way for extensions that "depend" on this extension to notify it of - // new cornerstone enabled elements so it's commands continue to work. - const handleOhifCornerstoneEnabledElementEvent = function(evt) { - const { context, viewportIndex, enabledElement } = evt.detail; - - setEnabledElement(viewportIndex, enabledElement, context); - }; - - document.addEventListener( - 'ohif-cornerstone-enabled-element-event', - handleOhifCornerstoneEnabledElementEvent - ); - - cs.events.addEventListener( - cs.EVENTS.ELEMENT_ENABLED, - elementEnabledHandler.bind(null, tools) - ); - cs.events.addEventListener( - cs.EVENTS.ELEMENT_DISABLED, - elementDisabledHandler - ); -} - -const _initMeasurementService = (MeasurementService, DisplaySetService) => { - /* Initialization */ - const { - Length, - Bidirectional, - EllipticalRoi, - ArrowAnnotate, - } = measurementServiceMappingsFactory(MeasurementService, DisplaySetService); - const csToolsVer4MeasurementSource = MeasurementService.createSource( - 'CornerstoneTools', - '4' - ); - - /* Mappings */ - MeasurementService.addMapping( - csToolsVer4MeasurementSource, - 'Length', - Length.matchingCriteria, - Length.toAnnotation, - Length.toMeasurement - ); - MeasurementService.addMapping( - csToolsVer4MeasurementSource, - 'Bidirectional', - Bidirectional.matchingCriteria, - Bidirectional.toAnnotation, - Bidirectional.toMeasurement - ); - - MeasurementService.addMapping( - csToolsVer4MeasurementSource, - 'EllipticalRoi', - EllipticalRoi.matchingCriteria, - EllipticalRoi.toAnnotation, - EllipticalRoi.toMeasurement - ); - - MeasurementService.addMapping( - csToolsVer4MeasurementSource, - 'ArrowAnnotate', - ArrowAnnotate.matchingCriteria, - ArrowAnnotate.toAnnotation, - ArrowAnnotate.toMeasurement - ); - - return csToolsVer4MeasurementSource; -}; - -const _connectToolsToMeasurementService = ( - MeasurementService, - DisplaySetService -) => { - const csToolsVer4MeasurementSource = _initMeasurementService( - MeasurementService, - DisplaySetService - ); - _connectMeasurementServiceToTools( - MeasurementService, - csToolsVer4MeasurementSource - ); - const { addOrUpdate, remove } = csToolsVer4MeasurementSource; - const elementEnabledEvt = cs.EVENTS.ELEMENT_ENABLED; - - /* Measurement Service Events */ - cs.events.addEventListener(elementEnabledEvt, evt => { - // TODO: Debounced update of measurements that are modified - function addMeasurement(csToolsEvent) { - console.log('CSTOOLS::addOrUpdate', csToolsEvent, csToolsEvent.detail); - - try { - const evtDetail = csToolsEvent.detail; - const { toolName, toolType, measurementData } = evtDetail; - const csToolName = toolName || measurementData.toolType || toolType; - - const measurementId = addOrUpdate(csToolName, evtDetail); - - if (measurementId) { - measurementData.id = measurementId; - } - } catch (error) { - console.warn('Failed to add measurement:', error); - } - } - - function updateMeasurement(csToolsEvent) { - try { - if (!csToolsEvent.detail.measurementData.id) { - return; - } - - const evtDetail = csToolsEvent.detail; - const { toolName, toolType, measurementData } = evtDetail; - const csToolName = toolName || measurementData.toolType || toolType; - - evtDetail.id = csToolsEvent.detail.measurementData.id; - addOrUpdate(csToolName, evtDetail); - } catch (error) { - console.warn('Failed to update measurement:', error); - } - } - - /** - * When csTools fires a removed event, remove the same measurement - * from the measurement service - * - * @param {*} csToolsEvent - */ - function removeMeasurement(csToolsEvent) { - console.log('~~ removeEvt', csToolsEvent); - try { - if (csToolsEvent.detail.measurementData.id) { - remove(csToolsEvent.detail.measurementData.id); - } - } catch (error) { - console.warn('Failed to remove measurement:', error); - } - } - - const { - MEASUREMENTS_CLEARED, - MEASUREMENT_UPDATED, - } = MeasurementService.EVENTS; - - MeasurementService.subscribe(MEASUREMENTS_CLEARED, () => { - globalImageIdSpecificToolStateManager.restoreToolState({}); - _refreshViewports(); + eventTarget.removeEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, evt => { + const { element } = evt.detail; + utilities.stackPrefetch.disable(element); }); + } - MeasurementService.subscribe( - MEASUREMENT_UPDATED, - ({ source, measurement, notYetUpdatedAtSource }) => { - const { id, label } = measurement; - - if ( - source.name == 'CornerstoneTools' && - notYetUpdatedAtSource === false - ) { - // This event was fired by cornerstone telling the measurement service to sync. Already in sync. - return; - } - const cornerstoneMeasurement = getCornerstoneMeasurementById(id); - - if (cornerstoneMeasurement) { - cornerstoneMeasurement.label = label; - if (cornerstoneMeasurement.hasOwnProperty('text')) { - // Deal with the weird case of ArrowAnnotate. - cornerstoneMeasurement.text = label; - } - - _refreshViewports(); - } - } - ); - - // on display sets added, check if there are any measurements in measurement service that need to be - // put into cornerstone tools tooldata - - const enabledElement = evt.detail.element; - const completedEvt = csTools.EVENTS.MEASUREMENT_COMPLETED; - const updatedEvt = csTools.EVENTS.MEASUREMENT_MODIFIED; - const removedEvt = csTools.EVENTS.MEASUREMENT_REMOVED; - - enabledElement.addEventListener(completedEvt, addMeasurement); - enabledElement.addEventListener(updatedEvt, updateMeasurement); - enabledElement.addEventListener(removedEvt, removeMeasurement); - }); - - return csToolsVer4MeasurementSource; -}; - -const _connectMeasurementServiceToTools = ( - MeasurementService, - measurementSource, - dataSource -) => { - const { - MEASUREMENT_REMOVED, - RAW_MEASUREMENT_ADDED, - } = MeasurementService.EVENTS; - const sourceId = measurementSource.id; - - // TODO: This is an unsafe delete - // Cornerstone-tools should probably expose a more generic "delete by id" - // And have toolState managers expose a method to find any of their toolState by ID - // --> csTools.deleteById --> internally checks all registered modules/managers? - // - // This implementation assumes a single globalImageIdSpecificToolStateManager - // It iterates all toolState for all toolTypes, and deletes any with a matching id - // - // Could potentially use "source" from event to determine tool type and skip some - // iterations? - - const { - POLYLINE, - ELLIPSE, - POINT, - BIDIRECTIONAL, - } = MeasurementService.VALUE_TYPES; - - // TODO -> I get why this was attemped, but its not nearly flexible enough. - // A single measurement may have an ellipse + a bidirectional measurement, for instances. - // You can't define a bidirectional tool as a single type.. - // OHIF-230 - const TOOL_TYPE_TO_VALUE_TYPE = { - Length: POLYLINE, - EllipticalRoi: ELLIPSE, - Bidirectional: BIDIRECTIONAL, - ArrowAnnotate: POINT, - }; - - const VALUE_TYPE_TO_TOOL_TYPE = { - [POLYLINE]: 'Length', - [ELLIPSE]: 'EllipticalRoi', - [BIDIRECTIONAL]: 'Bidirectional', - [POINT]: 'ArrowAnnotate', - }; - - MeasurementService.subscribe( - RAW_MEASUREMENT_ADDED, - ({ source, measurement, data, dataSource }) => { - const { - referenceStudyUID: StudyInstanceUID, - referenceSeriesUID: SeriesInstanceUID, - SOPInstanceUID, - } = measurement; - - let toolType; - try { - toolType = VALUE_TYPE_TO_TOOL_TYPE[measurement.type]; - } catch { - throw Error('Cannot add tool to cornerstone tools'); - } - - let imageId; - if (data.imageId) { - // handle dicom json launch, since we cannot create image id from - // instance UIDs, each tool should embed the instance metadata - // TODO: handle multi instance case - imageId = data.imageId; - } else { - // handle general case of dicom web - const instance = { - StudyInstanceUID, - SeriesInstanceUID, - SOPInstanceUID, - }; - // TODO: handle multi frame - imageId = dataSource.getImageIdsForInstance({ instance }); - } - - const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState(); - - if (toolState[imageId] === undefined) { - toolState[imageId] = {}; - } - - const imageIdToolState = toolState[imageId]; - - // If we don't have tool state for this type of tool, add an empty object - if (imageIdToolState[toolType] === undefined) { - imageIdToolState[toolType] = { - data: [], - }; - } - - const toolData = imageIdToolState[toolType]; - - toolData.data.push(data); - } - ); - - MeasurementService.subscribe( - MEASUREMENT_REMOVED, - ({ source, measurement: removedMeasurementId }) => { - // THIS POINTS TO ORIGINAL; Not a copy - const imageIdSpecificToolState = globalImageIdSpecificToolStateManager.saveToolState(); - - // ImageId --> - Object.keys(imageIdSpecificToolState).forEach(imageId => { - // ImageId --> Tool --> - Object.keys(imageIdSpecificToolState[imageId]).forEach(toolName => { - const toolState = imageIdSpecificToolState[imageId][toolName]; - - let annotationIndex = toolState.data.length - 1; - while (annotationIndex >= 0) { - const annotation = toolState.data[annotationIndex]; - - if (annotation.id === removedMeasurementId) { - toolState.data.splice(annotationIndex, 1); - } - - annotationIndex--; - } - }); - }); - } + eventTarget.addEventListener( + EVENTS.ELEMENT_ENABLED, + elementEnabledHandler.bind(null) ); -}; -const _getDefaultPosition = event => ({ - x: (event && event.currentPoints.client.x) || 0, - y: (event && event.currentPoints.client.y) || 0, -}); - -/** - * @private - */ -function _addConfiguredToolsForElement( - UIDialogService, - element, - tools, - configuration -) { - const internalToolsConfig = _createInternalToolsConfig(UIDialogService); - /* Add tools with its custom props through extension configuration. */ - tools.forEach(tool => { - const toolName = new tool().name; - const externalToolsConfig = configuration.tools || {}; - const externalToolProps = externalToolsConfig[toolName] || {}; - const internalToolProps = internalToolsConfig[toolName] || {}; - const props = merge( - internalToolProps, - _parseToolProps(configuration, externalToolProps, tool) - ); - csTools.addToolForElement(element, tool, props); - }); -} -/* Abstract tools configuration using extension configuration. */ -function _parseToolProps(configuration, props, tool) { - const { annotations } = toolsGroupedByType; - // An alternative approach would be to remove the `drawHandlesOnHover` config - // from the supported configuration properties in `cornerstone-tools` - const toolsWithHideableHandles = annotations.filter( - tool => !['RectangleRoiTool', 'EllipticalRoiTool'].includes(tool.name) + eventTarget.addEventListener( + EVENTS.ELEMENT_DISABLED, + elementDisabledHandler.bind(null) ); - - let parsedProps = { ...props }; - - /** - * drawHandles - Never/Always show handles - * drawHandlesOnHover - Only show handles on handle hover (pointNearHandle) - * hideHandlesIfMoving - Hides the handles whilst you are moving them, for better visibility. - * - * Does not apply to tools where handles aren't placed in predictable - * locations. - */ - if ( - configuration.hideHandles !== false && - toolsWithHideableHandles.includes(tool) - ) { - if (props.configuration) { - parsedProps.configuration.drawHandlesOnHover = true; - parsedProps.configuration.hideHandlesIfMoving = true; - } else { - parsedProps.configuration = { - drawHandlesOnHover: true, - hideHandlesIfMoving: true, - }; - } - } - - return parsedProps; } diff --git a/extensions/cornerstone/src/initCineService.ts b/extensions/cornerstone/src/initCineService.ts new file mode 100644 index 00000000000..75c1fdbb67e --- /dev/null +++ b/extensions/cornerstone/src/initCineService.ts @@ -0,0 +1,15 @@ +import { utilities } from '@cornerstonejs/tools'; + +function initCineService(CineService) { + const playClip = (element, playClipOptions) => { + return utilities.cine.playClip(element, playClipOptions); + }; + + const stopClip = element => { + return utilities.cine.stopClip(element); + }; + + CineService.setServiceImplementation({ playClip, stopClip }); +} + +export default initCineService; diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js index d2aa70c9573..a092976e678 100644 --- a/extensions/cornerstone/src/initCornerstoneTools.js +++ b/extensions/cornerstone/src/initCornerstoneTools.js @@ -1,47 +1,79 @@ -import Hammer from 'hammerjs'; -import cornerstone from 'cornerstone-core'; -import cornerstoneMath from 'cornerstone-math'; -import cornerstoneTools from 'cornerstone-tools'; -import OHIF from '@ohif/core'; +import { + PanTool, + WindowLevelTool, + StackScrollTool, + StackScrollMouseWheelTool, + ZoomTool, + VolumeRotateMouseWheelTool, + MIPJumpToClickTool, + LengthTool, + RectangleROITool, + EllipticalROITool, + BidirectionalTool, + ArrowAnnotateTool, + DragProbeTool, + AngleTool, + MagnifyTool, + CrosshairsTool, + SegmentationDisplayTool, + init, + addTool, + annotation, +} from '@cornerstonejs/tools'; -const { log } = OHIF; +export default function initCornerstoneTools(configuration = {}) { + init(configuration); + addTool(PanTool); + addTool(WindowLevelTool); + addTool(StackScrollMouseWheelTool); + addTool(StackScrollTool); + addTool(ZoomTool); + addTool(VolumeRotateMouseWheelTool); + addTool(MIPJumpToClickTool); + addTool(LengthTool); + addTool(RectangleROITool); + addTool(EllipticalROITool); + addTool(BidirectionalTool); + addTool(ArrowAnnotateTool); + addTool(DragProbeTool); + addTool(AngleTool); + addTool(MagnifyTool); + addTool(CrosshairsTool); + addTool(SegmentationDisplayTool); -export default function(configuration = {}) { - // TODO: Cypress tests are currently grabbing this from the window? - window.cornerstone = cornerstone; + // Modify annotation tools to use dashed lines on SR + const annotationStyle = { + textBoxFontSize: '15px', + lineWidth: '1.5', + }; - // For debugging - window.cornerstoneTools = cornerstoneTools; - - cornerstoneTools.external.cornerstone = cornerstone; - cornerstoneTools.external.Hammer = Hammer; - cornerstoneTools.external.cornerstoneMath = cornerstoneMath; - cornerstoneTools.init(configuration); - - cornerstoneTools.loadHandlerManager.setErrorLoadingHandler( - (element, imageId, error) => { - log.error(imageId); - throw error; - } - ); - - // Set the tool font and font size - // context.font = "[style] [variant] [weight] [size]/[line height] [font family]"; - const fontFamily = - 'Roboto, OpenSans, HelveticaNeue-Light, Helvetica Neue Light, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif'; - cornerstoneTools.textStyle.setFont(`16px ${fontFamily}`); - - // Tool styles/colors - cornerstoneTools.toolStyle.setToolWidth(2); - cornerstoneTools.toolColors.setToolColor('rgb(255, 255, 0)'); - cornerstoneTools.toolColors.setActiveColor('rgb(0, 255, 0)'); - - cornerstoneTools.store.state.touchProximity = 40; - - // Configure stack prefetch - cornerstoneTools.stackPrefetch.setConfiguration({ - maxImagesToPrefetch: Infinity, - preserveExistingPool: false, - maxSimultaneousRequests: 20, + const defaultStyles = annotation.config.style.getDefaultToolStyles(); + annotation.config.style.setDefaultToolStyles({ + global: { + ...defaultStyles.global, + ...annotationStyle, + }, }); } + +const toolNames = { + Pan: PanTool.toolName, + ArrowAnnotate: ArrowAnnotateTool.toolName, + WindowLevel: WindowLevelTool.toolName, + StackScroll: StackScrollTool.toolName, + StackScrollMouseWheel: StackScrollMouseWheelTool.toolName, + Zoom: ZoomTool.toolName, + VolumeRotateMouseWheel: VolumeRotateMouseWheelTool.toolName, + MipJumpToClick: MIPJumpToClickTool.toolName, + Length: LengthTool.toolName, + DragProbe: DragProbeTool.toolName, + RectangleROI: RectangleROITool.toolName, + EllipticalROI: EllipticalROITool.toolName, + Bidirectional: BidirectionalTool.toolName, + Angle: AngleTool.toolName, + Magnify: MagnifyTool.toolName, + Crosshairs: CrosshairsTool.toolName, + SegmentationDisplay: SegmentationDisplayTool.toolName, +}; + +export { toolNames }; diff --git a/extensions/cornerstone/src/initMeasurementService.js b/extensions/cornerstone/src/initMeasurementService.js new file mode 100644 index 00000000000..9b6d4a41465 --- /dev/null +++ b/extensions/cornerstone/src/initMeasurementService.js @@ -0,0 +1,296 @@ +import { eventTarget } from '@cornerstonejs/core'; +import { Enums, annotation } from '@cornerstonejs/tools'; +import { DicomMetadataStore } from '@ohif/core'; + +import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory'; + +const { removeAnnotation } = annotation.state; + +const csToolsEvents = Enums.Events; + +const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; +const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; + +const initMeasurementService = ( + MeasurementService, + DisplaySetService, + CornerstoneViewportService +) => { + /* Initialization */ + const { + Length, + Bidirectional, + EllipticalROI, + ArrowAnnotate, + } = measurementServiceMappingsFactory( + MeasurementService, + DisplaySetService, + CornerstoneViewportService + ); + const csTools3DVer1MeasurementSource = MeasurementService.createSource( + CORNERSTONE_3D_TOOLS_SOURCE_NAME, + CORNERSTONE_3D_TOOLS_SOURCE_VERSION + ); + + /* Mappings */ + MeasurementService.addMapping( + csTools3DVer1MeasurementSource, + 'Length', + Length.matchingCriteria, + Length.toAnnotation, + Length.toMeasurement + ); + + MeasurementService.addMapping( + csTools3DVer1MeasurementSource, + 'Bidirectional', + Bidirectional.matchingCriteria, + Bidirectional.toAnnotation, + Bidirectional.toMeasurement + ); + + MeasurementService.addMapping( + csTools3DVer1MeasurementSource, + 'EllipticalROI', + EllipticalROI.matchingCriteria, + EllipticalROI.toAnnotation, + EllipticalROI.toMeasurement + ); + + MeasurementService.addMapping( + csTools3DVer1MeasurementSource, + 'ArrowAnnotate', + ArrowAnnotate.matchingCriteria, + ArrowAnnotate.toAnnotation, + ArrowAnnotate.toMeasurement + ); + + return csTools3DVer1MeasurementSource; +}; + +const connectToolsToMeasurementService = ( + MeasurementService, + DisplaySetService, + CornerstoneViewportService +) => { + const csTools3DVer1MeasurementSource = initMeasurementService( + MeasurementService, + DisplaySetService, + CornerstoneViewportService + ); + connectMeasurementServiceToTools( + MeasurementService, + CornerstoneViewportService, + csTools3DVer1MeasurementSource + ); + const { annotationToMeasurement, remove } = csTools3DVer1MeasurementSource; + + // + function addMeasurement(csToolsEvent) { + try { + const annotationAddedEventDetail = csToolsEvent.detail; + const { + annotation: { metadata, annotationUID }, + } = annotationAddedEventDetail; + const { toolName } = metadata; + + // To force the measurementUID be the same as the annotationUID + // Todo: this should be changed when a measurement can include multiple annotations + // in the future + annotationAddedEventDetail.uid = annotationUID; + annotationToMeasurement(toolName, annotationAddedEventDetail); + } catch (error) { + console.warn('Failed to update measurement:', error); + } + } + function updateMeasurement(csToolsEvent) { + try { + const annotationModifiedEventDetail = csToolsEvent.detail; + + const { + annotation: { metadata, annotationUID }, + } = annotationModifiedEventDetail; + + // If the measurement hasn't been added, don't modify it + const measurement = MeasurementService.getMeasurement(annotationUID); + + if (!measurement) { + return; + } + const { toolName } = metadata; + + annotationModifiedEventDetail.uid = annotationUID; + annotationToMeasurement(toolName, annotationModifiedEventDetail); + } catch (error) { + console.warn('Failed to update measurement:', error); + } + } + + /** + * When csTools fires a removed event, remove the same measurement + * from the measurement service + * + * @param {*} csToolsEvent + */ + function removeMeasurement(csToolsEvent) { + try { + try { + const annotationRemovedEventDetail = csToolsEvent.detail; + const { + annotation: { annotationUID }, + } = annotationRemovedEventDetail; + + const measurement = MeasurementService.getMeasurement(annotationUID); + + if (measurement) { + console.log('~~ removeEvt', csToolsEvent); + remove(annotationUID, annotationRemovedEventDetail); + } + } catch (error) { + console.warn('Failed to update measurement:', error); + } + } catch (error) { + console.warn('Failed to remove measurement:', error); + } + } + + // on display sets added, check if there are any measurements in measurement service that need to be + // put into cornerstone tools + const completedEvt = csToolsEvents.ANNOTATION_COMPLETED; + const updatedEvt = csToolsEvents.ANNOTATION_MODIFIED; + const removedEvt = csToolsEvents.ANNOTATION_REMOVED; + + eventTarget.addEventListener(completedEvt, addMeasurement); + eventTarget.addEventListener(updatedEvt, updateMeasurement); + eventTarget.addEventListener(removedEvt, removeMeasurement); + + return csTools3DVer1MeasurementSource; +}; + +const connectMeasurementServiceToTools = ( + MeasurementService, + CornerstoneViewportService, + measurementSource +) => { + const { + MEASUREMENT_REMOVED, + MEASUREMENTS_CLEARED, + MEASUREMENT_UPDATED, + RAW_MEASUREMENT_ADDED, + } = MeasurementService.EVENTS; + + const csTools3DVer1MeasurementSource = MeasurementService.getSource( + CORNERSTONE_3D_TOOLS_SOURCE_NAME, + CORNERSTONE_3D_TOOLS_SOURCE_VERSION + ); + + MeasurementService.subscribe(MEASUREMENTS_CLEARED, ({ measurements }) => { + if (!Object.keys(measurements).length) { + return; + } + + for (const measurement of Object.values(measurements)) { + const { uid, source } = measurement; + if (source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) { + continue; + } + + removeAnnotation(uid); + } + }); + + MeasurementService.subscribe( + MEASUREMENT_UPDATED, + ({ source, measurement, notYetUpdatedAtSource }) => { + if (source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) { + return; + } + + if (notYetUpdatedAtSource === false) { + // This event was fired by cornerstone telling the measurement service to sync. + // Already in sync. + return; + } + + const { uid, label } = measurement; + + const sourceAnnotation = annotation.state.getAnnotation(uid); + const { data, metadata } = sourceAnnotation; + + if (!data) { + return; + } + + if (data.label !== label) { + data.label = label; + } + + if (metadata.toolName === 'ArrowAnnotate') { + data.text = label; + } + + // Todo: trigger render for annotation + } + ); + + MeasurementService.subscribe( + RAW_MEASUREMENT_ADDED, + ({ source, measurement, data, dataSource }) => { + if (source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) { + return; + } + + const { + referenceSeriesUID, + referenceStudyUID, + SOPInstanceUID, + } = measurement; + + const instance = DicomMetadataStore.getInstance( + referenceStudyUID, + referenceSeriesUID, + SOPInstanceUID + ); + + const imageId = dataSource.getImageIdsForInstance({ instance }); + const annotationManager = annotation.state.getDefaultAnnotationManager(); + annotationManager.addAnnotation({ + annotationUID: measurement.uid, + highlighted: false, + isLocked: false, + invalidated: false, + metadata: { + toolName: measurement.toolName, + FrameOfReferenceUID: measurement.FrameOfReferenceUID, + referencedImageId: imageId, + }, + data: { + text: data.annotation.data.text, + handles: { ...data.annotation.data.handles }, + cachedStats: { ...data.annotation.data.cachedStats }, + label: data.annotation.data.label, + }, + }); + } + ); + + MeasurementService.subscribe( + MEASUREMENT_REMOVED, + ({ source, measurement: removedMeasurementId }) => { + if (source?.name && source.name !== CORNERSTONE_3D_TOOLS_SOURCE_NAME) { + return; + } + removeAnnotation(removedMeasurementId); + const renderingEngine = CornerstoneViewportService.getRenderingEngine(); + // Note: We could do a better job by triggering the render on the + // viewport itself, but the removeAnnotation does not include that info... + renderingEngine.render(); + } + ); +}; + +export { + initMeasurementService, + connectToolsToMeasurementService, + connectMeasurementServiceToTools, +}; diff --git a/extensions/cornerstone/src/initSegmentationService.js b/extensions/cornerstone/src/initSegmentationService.js new file mode 100644 index 00000000000..3ea099fcec5 --- /dev/null +++ b/extensions/cornerstone/src/initSegmentationService.js @@ -0,0 +1,141 @@ +import { eventTarget, cache, triggerEvent } from '@cornerstonejs/core'; +import * as csTools from '@cornerstonejs/tools'; +import { Enums as csToolsEnums } from '@cornerstonejs/tools'; +import Labelmap from './utils/segmentationServiceMappings/Labelmap'; + +function initSegmentationService( + SegmentationService, + CornerstoneViewportService +) { + connectToolsToSegmentationService( + SegmentationService, + CornerstoneViewportService + ); + + connectSegmentationServiceToTools( + SegmentationService, + CornerstoneViewportService + ); +} + +function connectToolsToSegmentationService( + SegmentationService, + CornerstoneViewportService +) { + connectSegmentationServiceToTools( + SegmentationService, + CornerstoneViewportService + ); + const segmentationUpdated = csToolsEnums.Events.SEGMENTATION_MODIFIED; + + eventTarget.addEventListener(segmentationUpdated, evt => { + const { segmentationId } = evt.detail; + const segmentationState = csTools.segmentation.state.getSegmentation( + segmentationId + ); + + if (!segmentationState) { + return; + } + + if ( + !Object.keys(segmentationState.representationData).includes( + csToolsEnums.SegmentationRepresentations.Labelmap + ) + ) { + throw new Error('Non-labelmap representations are not supported yet'); + } + + // Todo: handle other representations when available in cornerstone3D + const segmentationSchema = Labelmap.toSegmentation(segmentationState); + + try { + SegmentationService.addOrUpdateSegmentation( + segmentationId, + segmentationSchema + ); + } catch (error) { + console.warn( + `Failed to add/update segmentation ${segmentationId}`, + error + ); + } + }); +} + +function connectSegmentationServiceToTools( + SegmentationService, + CornerstoneViewportService +) { + const { + SEGMENTATION_UPDATED, + SEGMENTATION_REMOVED, + } = SegmentationService.EVENTS; + + SegmentationService.subscribe(SEGMENTATION_REMOVED, ({ id }) => { + // Todo: This should be from the configuration + const removeFromCache = true; + + const sourceSegState = csTools.segmentation.state.getSegmentation(id); + + if (!sourceSegState) { + return; + } + + const toolGroupIds = csTools.segmentation.state.getToolGroupsWithSegmentation( + id + ); + + toolGroupIds.forEach(toolGroupId => { + const segmentationRepresentations = csTools.segmentation.state.getSegmentationRepresentations( + toolGroupId + ); + + const UIDsToRemove = []; + segmentationRepresentations.forEach(representation => { + if (representation.segmentationId === id) { + UIDsToRemove.push(representation.segmentationRepresentationUID); + } + }); + + csTools.segmentation.removeSegmentationsFromToolGroup( + toolGroupId, + UIDsToRemove + ); + }); + + // cleanup the segmentation state too + csTools.segmentation.state.removeSegmentation(id); + + if (removeFromCache) { + cache.removeVolumeLoadObject(id); + } + }); + + SegmentationService.subscribe( + SEGMENTATION_UPDATED, + ({ id, segmentation, notYetUpdatedAtSource }) => { + if (notYetUpdatedAtSource === false) { + return; + } + const { label, text } = segmentation; + + const sourceSegmentation = csTools.segmentation.state.getSegmentation(id); + + // Update the label in the source if necessary + if (sourceSegmentation.label !== label) { + sourceSegmentation.label = label; + } + + if (sourceSegmentation.text !== text) { + sourceSegmentation.text = text; + } + + triggerEvent(eventTarget, csTools.Enums.Events.SEGMENTATION_MODIFIED, { + segmentationId: id, + }); + } + ); +} + +export default initSegmentationService; diff --git a/extensions/cornerstone/src/initWADOImageLoader.js b/extensions/cornerstone/src/initWADOImageLoader.js index dadf7f26f30..f65f2accb3d 100644 --- a/extensions/cornerstone/src/initWADOImageLoader.js +++ b/extensions/cornerstone/src/initWADOImageLoader.js @@ -1,13 +1,20 @@ -import cornerstone from 'cornerstone-core'; +import * as cornerstone from '@cornerstonejs/core'; +import { volumeLoader } from '@cornerstonejs/core'; +import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader'; import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; import dicomParser from 'dicom-parser'; import { errorHandler } from '@ohif/core'; +const { registerVolumeLoader } = volumeLoader; + let initialized = false; -function initWebWorkers() { +function initWebWorkers(appConfig) { const config = { - maxWebWorkers: Math.max(navigator.hardwareConcurrency - 1, 1), + maxWebWorkers: Math.min( + Math.max(navigator.hardwareConcurrency - 1, 1), + appConfig.maxNumberOfWebWorkers + ), startWebWorkersOnDemand: true, taskConfiguration: { decodeTask: { @@ -24,11 +31,27 @@ function initWebWorkers() { } } -export default function initWADOImageLoader(UserAuthenticationService) { +export default function initWADOImageLoader( + UserAuthenticationService, + appConfig +) { cornerstoneWADOImageLoader.external.cornerstone = cornerstone; cornerstoneWADOImageLoader.external.dicomParser = dicomParser; + registerVolumeLoader( + 'cornerstoneStreamingImageVolume', + cornerstoneStreamingImageVolumeLoader + ); + cornerstoneWADOImageLoader.configure({ + decodeConfig: { + // !! IMPORTANT !! + // We should set this flag to false, since, by default cornerstone-wado-image-loader + // will convert everything to integers (to be able to work with cornerstone-2d). + // Until the default is set to true (which is the case for cornerstone3D), + // we should set this flag to false. + convertFloatPixelDataToInt: false, + }, beforeSend: function(xhr) { const headers = UserAuthenticationService.getAuthorizationHeader(); @@ -38,10 +61,7 @@ export default function initWADOImageLoader(UserAuthenticationService) { // For now we use image/jls and image/x-jls because some servers still use the old type // http://dicom.nema.org/medical/dicom/current/output/html/part18.html const xhrRequestHeaders = { - // To prevent Preflight requests: accept: 'multipart/related; type=application/octet-stream', - // - //accept: 'multipart/related; type="image/x-jls"', // 'multipart/related; type="image/x-jls", multipart/related; type="image/jls"; transfer-syntax="1.2.840.10008.1.2.4.80", multipart/related; type="image/x-jls", multipart/related; type="application/octet-stream"; transfer-syntax=*', }; @@ -56,5 +76,5 @@ export default function initWADOImageLoader(UserAuthenticationService) { }, }); - initWebWorkers(); + initWebWorkers(appConfig); } diff --git a/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts b/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts new file mode 100644 index 00000000000..d5256f77d5b --- /dev/null +++ b/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts @@ -0,0 +1,108 @@ +import { synchronizers, SynchronizerManager } from '@cornerstonejs/tools'; + +import { pubSubServiceInterface } from '@ohif/core'; + +const EVENTS = { + TOOL_GROUP_CREATED: 'event::cornerstone::syncgroupservice:toolgroupcreated', +}; + +export type SyncGroup = { + type: string; + id: string; + source: boolean; + target: boolean; +}; + +const POSITION = 'cameraposition'; +const VOI = 'voi'; + +export default class SyncGroupService { + serviceManager: any; + listeners: { [key: string]: (...args: any[]) => void } = {}; + EVENTS: { [key: string]: string }; + + constructor(serviceManager) { + this.serviceManager = serviceManager; + this.listeners = {}; + this.EVENTS = EVENTS; + // + Object.assign(this, pubSubServiceInterface); + } + + private _createSynchronizer(type: string, id: string) { + type = type.toLowerCase(); + if (type === POSITION) { + return synchronizers.createCameraPositionSynchronizer(id); + } else if (type === VOI) { + return synchronizers.createVOISynchronizer(id); + } + } + + public addViewportToSyncGroup( + viewportId: string, + renderingEngineId: string, + syncGroups?: SyncGroup[] + ): void { + if (!syncGroups || !syncGroups.length) { + return; + } + + syncGroups.forEach(syncGroup => { + const { type, id, target, source } = syncGroup; + + let synchronizer = SynchronizerManager.getSynchronizer(id); + + if (!synchronizer) { + synchronizer = this._createSynchronizer(type, id); + } + + if (target && source) { + synchronizer.add({ + viewportId, + renderingEngineId, + }); + return; + } else if (source) { + synchronizer.addSource({ + viewportId, + renderingEngineId, + }); + } else if (target) { + synchronizer.addTarget({ + viewportId, + renderingEngineId, + }); + } + }); + } + + public destroy() { + SynchronizerManager.destroy(); + } + + public removeViewportFromSyncGroup( + viewportId: string, + renderingEngineId: string + ): void { + const synchronizers = SynchronizerManager.getAllSynchronizers(); + + synchronizers.forEach(synchronizer => { + if (!synchronizer) { + return; + } + + synchronizer.remove({ + viewportId, + renderingEngineId, + }); + + // check if any viewport is left in any of the sync groups, if not, delete that sync group + const sourceViewports = synchronizer.getSourceViewports(); + const targetViewports = synchronizer.getTargetViewports(); + + if (!sourceViewports.length && !targetViewports.length) { + SynchronizerManager.destroySynchronizer(synchronizer.id); + } + }); + } +} diff --git a/extensions/cornerstone/src/services/SyncGroupService/index.js b/extensions/cornerstone/src/services/SyncGroupService/index.js new file mode 100644 index 00000000000..9a49fcca17a --- /dev/null +++ b/extensions/cornerstone/src/services/SyncGroupService/index.js @@ -0,0 +1,10 @@ +import SyncGroupService from './SyncGroupService'; + +export default function ExtendedSyncGroupService(serviceManager) { + return { + name: 'SyncGroupService', + create: ({ configuration = {} }) => { + return new SyncGroupService(serviceManager); + }, + }; +} diff --git a/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts new file mode 100644 index 00000000000..3e896a83896 --- /dev/null +++ b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts @@ -0,0 +1,260 @@ +import { ToolGroupManager, Enums, Types } from '@cornerstonejs/tools'; + +import { pubSubServiceInterface } from '@ohif/core'; + +const EVENTS = { + VIEWPORT_ADDED: 'event::cornerstone::toolgroupservice:viewportadded', + TOOLGROUP_CREATED: 'event::cornerstone::toolgroupservice:toolgroupcreated', +}; + +type Tool = { + toolName: string; + bindings?: typeof Enums.MouseBindings | Enums.KeyboardBindings; +}; + +type Tools = { + active: Tool[]; + passive?: Tool[]; + enabled?: Tool[]; + disabled?: Tool[]; +}; + +export default class ToolGroupService { + serviceManager: any; + private toolGroupIds: Set = new Set(); + /** + * Service-specific + */ + listeners: { [key: string]: Function[] }; + EVENTS: { [key: string]: string }; + + constructor(serviceManager) { + const { CornerstoneViewportService } = serviceManager.services; + this.CornerstoneViewportService = CornerstoneViewportService; + this.listeners = {}; + this.EVENTS = EVENTS; + Object.assign(this, pubSubServiceInterface); + } + + /** + * Returns the cornerstone ToolGroup for a given toolGroup UID + * @param {string} toolGroupId - The toolGroup uid + * @returns {IToolGroup} - The toolGroup + */ + public getToolGroup(toolGroupId: string): Types.IToolGroup | void { + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + return toolGroup; + } + + public getToolGroupIds(): string[] { + return Array.from(this.toolGroupIds); + } + + public getToolGroupForViewport(viewportId: string): Types.IToolGroup | void { + const renderingEngine = this.CornerstoneViewportService.getRenderingEngine(); + return ToolGroupManager.getToolGroupForViewport( + viewportId, + renderingEngine.id + ); + } + + public getActiveToolForViewport(viewportId: string): string { + const toolGroup = ToolGroupManager.getToolGroupForViewport(viewportId); + if (!toolGroup) { + return null; + } + + return toolGroup.getActivePrimaryMouseButtonTool(); + } + + public destroy() { + ToolGroupManager.destroy(); + this.toolGroupIds = new Set(); + } + + public disable(viewportId: string, renderingEngineId: string): void { + const toolGroup = ToolGroupManager.getToolGroupForViewport( + viewportId, + renderingEngineId + ); + + if (!toolGroup) { + return; + } + + toolGroup.removeViewports(renderingEngineId, viewportId); + + const viewportIds = toolGroup.getViewportIds(); + // if (viewportIds.length === 0) { + // ToolGroupManager.destroyToolGroup(toolGroup.id); + // } + } + + public addViewportToToolGroup( + viewportId: string, + renderingEngineId: string, + toolGroupId?: string + ): void { + if (!toolGroupId) { + // If toolGroupId is not provided, add the viewport to all toolGroups + const toolGroups = ToolGroupManager.getAllToolGroups(); + toolGroups.forEach(toolGroup => { + toolGroup.addViewport(viewportId, renderingEngineId); + }); + } else { + let toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + if (!toolGroup) { + toolGroup = this.createToolGroup(toolGroupId); + } + + toolGroup.addViewport(viewportId, renderingEngineId); + } + + this._broadcastEvent(EVENTS.VIEWPORT_ADDED, { viewportId, toolGroupId }); + } + + public createToolGroup(toolGroupId: string): Types.IToolGroup { + if (this.getToolGroup(toolGroupId)) { + throw new Error(`ToolGroup ${toolGroupId} already exists`); + } + + // if the toolGroup doesn't exist, create it + const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); + this.toolGroupIds.add(toolGroupId); + + this._broadcastEvent(EVENTS.TOOLGROUP_CREATED, { toolGroupId }); + + return toolGroup; + } + + public addToolsToToolGroup( + toolGroupId: string, + tools: Array, + configs: any = {} + ): void { + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + // this.changeConfigurationIfNecessary(toolGroup, volumeId); + this._addTools(toolGroup, tools, configs); + this._setToolsMode(toolGroup, tools); + } + + public createToolGroupAndAddTools( + toolGroupId: string, + tools: Array, + configs: any = {} + ): Types.IToolGroup { + const toolGroup = this.createToolGroup(toolGroupId); + this.addToolsToToolGroup(toolGroupId, tools, configs); + return toolGroup; + } + + /** + private changeConfigurationIfNecessary(toolGroup, volumeUID) { + // handle specific assignment for volumeUID (e.g., fusion) + const toolInstances = toolGroup._toolInstances; + // Object.values(toolInstances).forEach(toolInstance => { + // if (toolInstance.configuration) { + // toolInstance.configuration.volumeUID = volumeUID; + // } + // }); + } + */ + + /** + * Get the tool's configuration based on the tool name and tool group id + * @param toolGroupId - The id of the tool group that the tool instance belongs to. + * @param toolName - The name of the tool + * @returns The configuration of the tool. + */ + public getToolConfiguration(toolGroupId: string, toolName: string) { + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + if (!toolGroup) { + return null; + } + + const tool = toolGroup.getToolInstance(toolName); + if (!tool) { + return null; + } + + return tool.configuration; + } + + /** + * Set the tool instance configuration. This will update the tool instance configuration + * on the toolGroup + * @param toolGroupId - The id of the tool group that the tool instance belongs to. + * @param toolName - The name of the tool + * @param config - The configuration object that you want to set. + */ + public setToolConfiguration(toolGroupId, toolName, config) { + const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); + const toolInstance = toolGroup.getToolInstance(toolName); + toolInstance.configuration = config; + } + + private _getToolNames(toolGroupTools: Tools): string[] { + const toolNames = []; + toolGroupTools.active.forEach(tool => { + toolNames.push(tool.toolName); + }); + if (toolGroupTools.passive) { + toolGroupTools.passive.forEach(tool => { + toolNames.push(tool.toolName); + }); + } + + if (toolGroupTools.enabled) { + toolGroupTools.enabled.forEach(tool => { + toolNames.push(tool.toolName); + }); + } + + if (toolGroupTools.disabled) { + toolGroupTools.disabled.forEach(tool => { + toolNames.push(tool.toolName); + }); + } + + return toolNames; + } + + private _setToolsMode(toolGroup, tools) { + const { active, passive, enabled, disabled } = tools; + active.forEach(({ toolName, bindings }) => { + toolGroup.setToolActive(toolName, { bindings }); + }); + + if (passive) { + passive.forEach(({ toolName }) => { + toolGroup.setToolPassive(toolName); + }); + } + + if (enabled) { + enabled.forEach(({ toolName }) => { + toolGroup.setToolEnabled(toolName); + }); + } + + if (disabled) { + disabled.forEach(({ toolName }) => { + toolGroup.setToolDisabled(toolName); + }); + } + } + + private _addTools(toolGroup, tools, configs) { + const toolNames = this._getToolNames(tools); + toolNames.forEach(toolName => { + // Initialize the toolConfig if no configuration is provided + const toolConfig = configs[toolName] ?? {}; + + // if (volumeUID) { + // toolConfig.volumeUID = volumeUID; + // } + + toolGroup.addTool(toolName, { ...toolConfig }); + }); + } +} diff --git a/extensions/cornerstone/src/services/ToolGroupService/index.js b/extensions/cornerstone/src/services/ToolGroupService/index.js new file mode 100644 index 00000000000..b57a6fecf6d --- /dev/null +++ b/extensions/cornerstone/src/services/ToolGroupService/index.js @@ -0,0 +1,10 @@ +import ToolGroupService from './ToolGroupService'; + +export default function ExtendedToolGroupService(serviceManager) { + return { + name: 'ToolGroupService', + create: ({ configuration = {} }) => { + return new ToolGroupService(serviceManager); + }, + }; +} diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneCacheService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneCacheService.ts new file mode 100644 index 00000000000..ba19dfa4954 --- /dev/null +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneCacheService.ts @@ -0,0 +1,227 @@ +import { + cache as cs3DCache, + Enums, + Types, + volumeLoader, +} from '@cornerstonejs/core'; +import { utils, pubSubServiceInterface } from '@ohif/core'; + +import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType'; + +export type StackData = { + StudyInstanceUID: string; + displaySetInstanceUID: string; + imageIds: string[]; + frameRate?: number; + isClip?: boolean; + initialImageIndex?: number | string | null; + viewportType: Enums.ViewportType; +}; + +export type VolumeData = { + StudyInstanceUID: string; + displaySetInstanceUIDs: string[]; // can have more than one displaySet (fusion) + imageIds: string[][]; // can have more than one imageId list (fusion) + volumes: Types.IVolume[]; + viewportType: Enums.ViewportType; +}; + +const VOLUME_LOADER_SCHEME = 'streaming-wadors'; + +const EVENTS = { + VIEWPORT_DATA_CHANGED: 'event::cornerstone::viewportdatachanged', +}; + +class CornerstoneCacheService { + stackImageIds: Map = new Map(); + volumeImageIds: Map = new Map(); + listeners: { [key: string]: (...args: any[]) => void } = {}; + EVENTS: { [key: string]: string }; + + constructor() { + this.listeners = {}; + this.EVENTS = EVENTS; + Object.assign(this, pubSubServiceInterface); + } + + public getCacheSize() { + return cs3DCache.getCacheSize(); + } + + public getCacheFreeSpace() { + return cs3DCache.getBytesAvailable(); + } + + public async getViewportData( + viewportIndex: number, + displaySets: unknown[], + viewportType: string, + dataSource: unknown, + initialImageIndex?: number + ): Promise { + const cs3DViewportType = getCornerstoneViewportType(viewportType); + let viewportData: StackData | VolumeData; + + if (cs3DViewportType === Enums.ViewportType.STACK) { + viewportData = await this._getStackViewportData( + dataSource, + displaySets, + initialImageIndex + ); + } + + if (cs3DViewportType === Enums.ViewportType.ORTHOGRAPHIC) { + viewportData = await this._getVolumeViewportData(dataSource, displaySets); + } + + viewportData.viewportType = cs3DViewportType; + + this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { + viewportData, + viewportIndex, + }); + + return viewportData; + } + + public async invalidateViewportData( + viewportData: VolumeData, + invalidatedDisplaySetInstanceUID: string, + dataSource, + DisplaySetService + ) { + if (viewportData.viewportType === Enums.ViewportType.STACK) { + throw new Error('Invalidation of StackViewport is not supported yet'); + } + + const volumeId = invalidatedDisplaySetInstanceUID; + const volume = cs3DCache.getVolume(volumeId); + + if (volume) { + cs3DCache.removeVolumeLoadObject(volumeId); + } + + const displaySets = viewportData.displaySetInstanceUIDs.map( + DisplaySetService.getDisplaySetByUID + ); + + const newViewportData = await this._getVolumeViewportData( + dataSource, + displaySets + ); + + return newViewportData; + } + + private _getStackViewportData( + dataSource, + displaySets, + initialImageIndex + ): StackData { + // For Stack Viewport we don't have fusion currently + const displaySet = displaySets[0]; + + let stackImageIds = this.stackImageIds.get( + displaySet.displaySetInstanceUID + ); + + if (!stackImageIds) { + stackImageIds = this._getCornerstoneStackImageIds(displaySet, dataSource); + this.stackImageIds.set(displaySet.displaySetInstanceUID, stackImageIds); + } + + const { displaySetInstanceUID, StudyInstanceUID } = displaySet; + + const stackData: StackData = { + StudyInstanceUID, + displaySetInstanceUID, + viewportType: Enums.ViewportType.STACK, + imageIds: stackImageIds, + }; + + if (typeof initialImageIndex === 'number') { + stackData.initialImageIndex = initialImageIndex; + } + + return stackData; + } + + private async _getVolumeViewportData( + dataSource, + displaySets + ): Promise { + // Check the cache for multiple scenarios to see if we need to + // decache the volume data from other viewports or not + + const volumeImageIdsArray = []; + const volumes = []; + + for (const displaySet of displaySets) { + const volumeId = displaySet.displaySetInstanceUID; + + let volumeImageIds = this.volumeImageIds.get( + displaySet.displaySetInstanceUID + ); + + let volume = cs3DCache.getVolume(volumeId); + + if (!volumeImageIds || !volume) { + volumeImageIds = this._getCornerstoneVolumeImageIds( + displaySet, + dataSource + ); + + volume = await volumeLoader.createAndCacheVolume(volumeId, { + imageIds: volumeImageIds, + }); + + this.volumeImageIds.set( + displaySet.displaySetInstanceUID, + volumeImageIds + ); + } + + volumeImageIdsArray.push(volumeImageIds); + volumes.push(volume); + } + + // assert displaySets are from the same study + const { StudyInstanceUID } = displaySets[0]; + const displaySetInstanceUIDs = []; + + displaySets.forEach(displaySet => { + if (displaySet.StudyInstanceUID !== StudyInstanceUID) { + throw new Error('Display sets are not from the same study'); + } + + displaySetInstanceUIDs.push(displaySet.displaySetInstanceUID); + }); + + return { + StudyInstanceUID, + displaySetInstanceUIDs, + imageIds: volumeImageIdsArray, + viewportType: Enums.ViewportType.ORTHOGRAPHIC, + volumes, + }; + } + + private _getCornerstoneStackImageIds(displaySet, dataSource): string[] { + return dataSource.getImageIdsForDisplaySet(displaySet); + } + + private _getCornerstoneVolumeImageIds(displaySet, dataSource): string[] { + const stackImageIds = this._getCornerstoneStackImageIds( + displaySet, + dataSource + ); + + return stackImageIds.map(imageId => { + const imageURI = utils.imageIdToURI(imageId); + return `${VOLUME_LOADER_SCHEME}:${imageURI}`; + }); + } +} + +const CacheService = new CornerstoneCacheService(); +export default CacheService; diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts new file mode 100644 index 00000000000..2cd56f82330 --- /dev/null +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -0,0 +1,579 @@ +import { pubSubServiceInterface } from '@ohif/core'; +import { + RenderingEngine, + StackViewport, + Enums, + Types, + getRenderingEngine, + utilities as csUtils, + VolumeViewport, + cache, +} from '@cornerstonejs/core'; + +import { utilities as csToolsUtils } from '@cornerstonejs/tools'; +import { IViewportService } from './IViewportService'; +import { RENDERING_ENGINE_ID } from './constants'; +import ViewportInfo, { + ViewportOptions, + DisplaySetOptions, + PublicViewportOptions, +} from './Viewport'; +import { StackData, VolumeData } from './CornerstoneCacheService'; +import { + setColormap, + setLowerUpperColorTransferFunction, +} from '../../utils/colormap/transferFunctionHelpers'; + +import JumpPresets from '../../utils/JumpPresets'; + +const EVENTS = { + VIEWPORT_INFO_CREATED: + 'event::cornerstone::viewportservice:viewportinfocreated', +}; + +/** + * Handles cornerstone viewport logic including enabling, disabling, and + * updating the viewport. + */ +class CornerstoneViewportService implements IViewportService { + renderingEngine: Types.IRenderingEngine | null; + viewportsInfo: Map; + viewportGridResizeObserver: ResizeObserver | null; + + /** + * Service-specific + */ + EVENTS: { [key: string]: string }; + listeners: { [key: string]: Array<(...args: any[]) => void> }; + _broadcastEvent: unknown; // we should be able to extend the PubSub class to get this + // Some configs + enableResizeDetector: true; + resizeRefreshRateMs: 200; + resizeRefreshMode: 'debounce'; + + constructor(servicesManager) { + this.renderingEngine = null; + this.viewportGridResizeObserver = null; + this.viewportsInfo = new Map(); + // + this.listeners = {}; + this.EVENTS = EVENTS; + const { HangingProtocolService } = servicesManager.services; + this.HangingProtocolService = HangingProtocolService; + Object.assign(this, pubSubServiceInterface); + // + } + + /** + * Adds the HTML element to the viewportService + * @param {*} viewportIndex + * @param {*} elementRef + */ + public enableElement( + viewportIndex: number, + viewportOptions: PublicViewportOptions, + elementRef: HTMLDivElement + ) { + const viewportId = + viewportOptions.viewportId || this.getViewportId(viewportIndex); + const viewportInfo = new ViewportInfo(viewportIndex, viewportId); + viewportInfo.setElement(elementRef); + this.viewportsInfo.set(viewportIndex, viewportInfo); + } + + public getViewportId(viewportIndex: number): string { + return `viewport-${viewportIndex}`; + } + + /** + * It retrieves the renderingEngine if it does exist, or creates one otherwise + * @returns {RenderingEngine} rendering engine + */ + public getRenderingEngine() { + // get renderingEngine from cache if it exists + const renderingEngine = getRenderingEngine(RENDERING_ENGINE_ID); + + if (renderingEngine) { + this.renderingEngine = renderingEngine; + return this.renderingEngine; + } + + if (!renderingEngine || renderingEngine.hasBeenDestroyed) { + this.renderingEngine = new RenderingEngine(RENDERING_ENGINE_ID); + } + + return this.renderingEngine; + } + + /** + * It triggers the resize on the rendering engine. + */ + public resize() { + const immediate = true; + const resetPan = false; + const resetZoom = false; + + this.renderingEngine.resize(immediate, resetPan, resetZoom); + this.renderingEngine.render(); + } + + /** + * Removes the viewport from cornerstone, and destroys the rendering engine + */ + public destroy() { + this._removeResizeObserver(); + this.viewportGridResizeObserver = null; + this.renderingEngine.destroy(); + this.renderingEngine = null; + cache.purgeVolumeCache(); + } + + /** + * Disables the viewport inside the renderingEngine, if no viewport is left + * it destroys the renderingEngine. + * @param viewportIndex + */ + public disableElement(viewportIndex: number) { + const viewportInfo = this.viewportsInfo.get(viewportIndex); + if (!viewportInfo) { + return; + } + + const viewportId = viewportInfo.getViewportId(); + this.renderingEngine.disableElement(viewportId); + this.viewportsInfo.delete(viewportIndex); + + if (this.viewportsInfo.size === 0) { + this.destroy(); + } + } + + /** + * Uses the renderingEngine to enable the element for the given viewport index + * and sets the displaySet data to the viewport + * @param {*} viewportIndex + * @param {*} displaySet + * @param {*} dataSource + * @returns + */ + public setViewportDisplaySets( + viewportIndex: number, + viewportData: StackData | VolumeData, + publicViewportOptions: PublicViewportOptions, + publicDisplaySetOptions: DisplaySetOptions[] + ): void { + const renderingEngine = this.getRenderingEngine(); + const viewportInfo = this.viewportsInfo.get(viewportIndex); + viewportInfo.setRenderingEngineId(renderingEngine.id); + + const { + viewportOptions, + displaySetOptions, + } = this._getViewportAndDisplaySetOptions( + publicViewportOptions, + publicDisplaySetOptions, + viewportInfo + ); + + viewportInfo.setViewportOptions(viewportOptions); + viewportInfo.setDisplaySetOptions(displaySetOptions); + + this._broadcastEvent(EVENTS.VIEWPORT_INFO_CREATED, viewportInfo); + + const viewportId = viewportInfo.getViewportId(); + const element = viewportInfo.getElement(); + const type = viewportInfo.getViewportType(); + const background = viewportInfo.getBackground(); + const orientation = viewportInfo.getOrientation(); + + const viewportInput: Types.PublicViewportInput = { + viewportId, + element, + type, + defaultOptions: { + background, + orientation, + }, + }; + + // Todo: this is not optimal at all, we are re-enabling the already enabled + // element which is not what we want. But enabledElement as part of the + // renderingEngine is designed to be used like this. This will trigger + // ENABLED_ELEMENT again and again, which will run onEnableElement callbacks + renderingEngine.enableElement(viewportInput); + this._setDisplaySets(viewportId, viewportData, viewportInfo); + } + + public getCornerstoneViewport( + viewportId: string + ): Types.IStackViewport | Types.IVolumeViewport | null { + const viewportInfo = this.getViewportInfo(viewportId); + + if ( + !viewportInfo || + !this.renderingEngine || + this.renderingEngine.hasBeenDestroyed + ) { + return null; + } + + const viewport = this.renderingEngine.getViewport(viewportId); + + return viewport; + } + + public getCornerstoneViewportByIndex( + viewportIndex: number + ): Types.IStackViewport | Types.IVolumeViewport | null { + const viewportInfo = this.getViewportInfoByIndex(viewportIndex); + + if ( + !viewportInfo || + !this.renderingEngine || + this.renderingEngine.hasBeenDestroyed + ) { + return null; + } + + const viewport = this.renderingEngine.getViewport( + viewportInfo.getViewportId() + ); + + return viewport; + } + + /** + * Returns the viewportIndex for the provided viewportId + * @param {string} viewportId - the viewportId + * @returns {number} - the viewportIndex + */ + public getViewportInfoByIndex(viewportIndex: number): ViewportInfo { + return this.viewportsInfo.get(viewportIndex); + } + + public getViewportInfo(viewportId: string): ViewportInfo { + // @ts-ignore + for (const [index, viewport] of this.viewportsInfo.entries()) { + if (viewport.getViewportId() === viewportId) { + return viewport; + } + } + return null; + } + + _setStackViewport( + viewport: Types.IStackViewport, + viewportData: StackData, + viewportInfo: ViewportInfo + ) { + const displaySetOptions = viewportInfo.getDisplaySetOptions(); + + const { imageIds, initialImageIndex } = viewportData; + + let initialImageIndexToUse = initialImageIndex; + + if (!initialImageIndexToUse) { + initialImageIndexToUse = + this._getInitialImageIndexForStackViewport(viewportInfo, imageIds) || 0; + } + + const { voi, voiInverted } = displaySetOptions[0]; + const properties = {}; + if (voi && (voi.windowWidth || voi.windowCenter)) { + const { lower, upper } = csUtils.windowLevel.toLowHighRange( + voi.windowWidth, + voi.windowCenter + ); + properties.voiRange = { lower, upper }; + } + + if (voiInverted !== undefined) { + properties.invert = voiInverted; + } + + viewport.setStack(imageIds, initialImageIndexToUse).then(() => { + viewport.setProperties(properties); + }); + } + + private _getInitialImageIndexForStackViewport( + viewportInfo: ViewportInfo, + imageIds?: string[] + ): number { + const initialImageOptions = viewportInfo.getInitialImageOptions(); + + if (!initialImageOptions) { + return; + } + + const { index, preset } = initialImageOptions; + return this._getInitialImageIndex(imageIds.length, index, preset); + } + + _getInitialImageIndex( + numberOfSlices: number, + imageIndex?: number, + preset?: JumpPresets + ): number { + const lastSliceIndex = numberOfSlices - 1; + + if (imageIndex !== undefined) { + return csToolsUtils.clip(imageIndex, 0, lastSliceIndex); + } + + if (preset === JumpPresets.First) { + return 0; + } + + if (preset === JumpPresets.Last) { + return lastSliceIndex; + } + + if (preset === JumpPresets.Middle) { + return Math.floor(lastSliceIndex / 2); + } + + return 0; + } + + async _setVolumeViewport( + viewport: Types.IVolumeViewport, + viewportData: VolumeData, + viewportInfo: ViewportInfo + ): Promise { + // TODO: We need to overhaul the way data sources work so requests can be made + // async. I think we should follow the image loader pattern which is async and + // has a cache behind it. + // The problem is that to set this volume, we need the metadata, but the request is + // already in-flight, and the promise is not cached, so we have no way to wait for + // it and know when it has fully arrived. + // loadStudyMetadata(StudyInstanceUID) => Promise([instances for study]) + // loadSeriesMetadata(StudyInstanceUID, SeriesInstanceUID) => Promise([instances for series]) + // If you call loadStudyMetadata and it's not in the DicomMetadataStore cache, it should fire + // a request through the data source? + // (This call may or may not create sub-requests for series metadata) + const volumeInputArray = []; + const displaySetOptionsArray = viewportInfo.getDisplaySetOptions(); + const { HangingProtocolService } = this; + + for (let i = 0; i < viewportData.imageIds.length; i++) { + const imageIds = viewportData.imageIds[i]; + const displaySetInstanceUID = viewportData.displaySetInstanceUIDs[i]; + const displaySetOptions = displaySetOptionsArray[i]; + + const volumeId = displaySetInstanceUID; + + // if (displaySet.needsRerendering) { + // console.warn('Removing volume from cache', volumeId); + // cache.removeVolumeLoadObject(volumeId); + // displaySet.needsRerendering = false; + // this.displaySetsNeedRerendering.add(displaySet.displaySetInstanceUID); + // } + + const voiCallbacks = this._getVOICallbacks(volumeId, displaySetOptions); + + const callback = ({ volumeActor }) => { + voiCallbacks.forEach(callback => callback(volumeActor)); + }; + + volumeInputArray.push({ + imageIds, + volumeId, + callback, + blendMode: displaySetOptions.blendMode, + slabThickness: this._getSlabThickness(displaySetOptions, volumeId), + }); + } + + if ( + HangingProtocolService.hasCustomImageLoadStrategy() && + !HangingProtocolService.customImageLoadPerformed + ) { + // delegate the volume loading to the hanging protocol service if it has a custom image load strategy + return HangingProtocolService.runImageLoadStrategy({ + viewportId: viewport.id, + volumeInputArray, + }); + } + + viewportData.volumes.forEach(volume => { + volume.load(); + }); + + this.setVolumesForViewport(viewport, volumeInputArray); + } + + public setVolumesForViewport(viewport, volumeInputArray) { + viewport.setVolumes(volumeInputArray).then(() => { + const viewportInfo = this.getViewportInfo(viewport.id); + const initialImageOptions = viewportInfo.getInitialImageOptions(); + + if ( + initialImageOptions && + (initialImageOptions.preset !== undefined || + initialImageOptions.index !== undefined) + ) { + const { index, preset } = initialImageOptions; + + const { numberOfSlices } = csUtils.getImageSliceDataForVolumeViewport( + viewport + ); + + const imageIndex = this._getInitialImageIndex( + numberOfSlices, + index, + preset + ); + + csToolsUtils.jumpToSlice(viewport.element, { + imageIndex, + }); + } + + viewport.render(); + }); + } + + public updateViewport(viewportIndex, viewportData) { + const viewportInfo = this.getViewportInfoByIndex(viewportIndex); + + const viewportId = viewportInfo.getViewportId(); + const viewport = this.getCornerstoneViewport(viewportId); + + if (viewport instanceof VolumeViewport) { + this._setVolumeViewport(viewport, viewportData, viewportInfo); + return; + } + + if (viewport instanceof StackViewport) { + this._setStackViewport(viewport, viewportData, viewportInfo); + return; + } + } + + _getVOICallbacks(volumeId, displaySetOptions) { + const { voi, voiInverted: inverted, colormap } = displaySetOptions; + + const voiCallbackArray = []; + + // If colormap is set, use it to set the color transfer function + if (colormap) { + voiCallbackArray.push(volumeActor => setColormap(volumeActor, colormap)); + } + + if (voi instanceof Object && voi.windowWidth && voi.windowCenter) { + const { windowWidth, windowCenter } = voi; + const { lower, upper } = csUtils.windowLevel.toLowHighRange( + windowWidth, + windowCenter + ); + voiCallbackArray.push(volumeActor => + setLowerUpperColorTransferFunction({ + volumeActor, + lower, + upper, + inverted, + }) + ); + } + + return voiCallbackArray; + } + + _setDisplaySets( + viewportId: string, + viewportData: StackData | VolumeData, + viewportInfo: ViewportInfo + ): void { + const viewport = this.getCornerstoneViewport(viewportId); + + if (viewport instanceof StackViewport) { + this._setStackViewport(viewport, viewportData as StackData, viewportInfo); + } else if (viewport instanceof VolumeViewport) { + this._setVolumeViewport( + viewport, + viewportData as VolumeData, + viewportInfo + ); + } else { + throw new Error('Unknown viewport type'); + } + } + + /** + * Removes the resize observer from the viewport element + */ + _removeResizeObserver() { + if (this.viewportGridResizeObserver) { + this.viewportGridResizeObserver.disconnect(); + } + } + + _getSlabThickness(displaySetOptions, volumeId) { + const { blendMode } = displaySetOptions; + if ( + blendMode === undefined || + displaySetOptions.slabThickness === undefined + ) { + return; + } + + // if there is a slabThickness set as a number then use it + if (typeof displaySetOptions.slabThickness === 'number') { + return displaySetOptions.slabThickness; + } + + if (displaySetOptions.slabThickness.toLowerCase() === 'fullvolume') { + // calculate the slab thickness based on the volume dimensions + const imageVolume = cache.getVolume(volumeId); + + const { dimensions } = imageVolume; + const slabThickness = Math.sqrt( + dimensions[0] * dimensions[0] + + dimensions[1] * dimensions[1] + + dimensions[2] * dimensions[2] + ); + + return slabThickness; + } + } + + _getViewportAndDisplaySetOptions( + publicViewportOptions: PublicViewportOptions, + publicDisplaySetOptions: DisplaySetOptions[], + viewportInfo: ViewportInfo + ): { + viewportOptions: ViewportOptions; + displaySetOptions: DisplaySetOptions[]; + } { + const viewportIndex = viewportInfo.getViewportIndex(); + + // Creating a temporary viewportInfo to handle defaults + const newViewportInfo = new ViewportInfo( + viewportIndex, + viewportInfo.getViewportId() + ); + + // To handle setting the default values if missing for the viewportOptions and + // displaySetOptions + newViewportInfo.setPublicViewportOptions(publicViewportOptions); + newViewportInfo.setPublicDisplaySetOptions(publicDisplaySetOptions); + + const newViewportOptions = newViewportInfo.getViewportOptions(); + const newDisplaySetOptions = newViewportInfo.getDisplaySetOptions(); + + return { + viewportOptions: newViewportOptions, + displaySetOptions: newDisplaySetOptions, + }; + } +} + +export default function ExtendedCornerstoneViewportService(serviceManager) { + return { + name: 'CornerstoneViewportService', + create: ({ configuration = {} }) => { + return new CornerstoneViewportService(serviceManager); + }, + }; +} diff --git a/extensions/cornerstone/src/services/ViewportService/IViewportService.ts b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts new file mode 100644 index 00000000000..ce47f28fcad --- /dev/null +++ b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts @@ -0,0 +1,74 @@ +import { Types } from '@cornerstonejs/core'; +import { StackData, VolumeData } from './CornerstoneCacheService'; +import { + DisplaySetOptions, + PublicViewportOptions, + ViewportOptions, +} from './Viewport'; + +/** + * Handles cornerstone viewport logic including enabling, disabling, and + * updating the viewport. + */ +export interface IViewportService { + servicesManager: unknown; + HangingProtocolService: unknown; + renderingEngine: unknown; + viewportGridResizeObserver: unknown; + viewportsInfo: unknown; + sceneVolumeInputs: unknown; + viewportIndexUIDMap: unknown; + viewportDivElements: unknown; + ViewportPropertiesMap: unknown; + volumeUIDs: unknown; + listeners: { [key: string]: {} }; + displaySetsNeedRerendering: unknown; + viewportDisplaySets: unknown; + EVENTS: { [key: string]: string }; + _broadcastEvent: unknown; + /** + * Adds the HTML element to the viewportService + * @param {*} viewportIndex + * @param {*} elementRef + */ + enableElement( + viewportIndex: number, + viewportOptions: ViewportOptions, + elementRef: HTMLDivElement + ): void; + /** + * It retrieves the renderingEngine if it does exist, or creates one otherwise + * @returns {RenderingEngine} rendering engine + */ + getRenderingEngine(): Types.IRenderingEngine; + /** + * It creates a resize observer for the viewport element, and observes + * the element for resizing events + * @param {*} elementRef + */ + resize(element: HTMLDivElement): void; + /** + * Removes the viewport from cornerstone, and destroys the rendering engine + */ + destroy(): void; + /** + * Disables the viewport inside the renderingEngine, if no viewport is left + * it destroys the renderingEngine. + * @param viewportIndex + */ + disableElement(viewportIndex: number): void; + /** + * Uses the renderingEngine to enable the element for the given viewport index + * and sets the displaySet data to the viewport + * @param {*} viewportIndex + * @param {*} displaySet + * @param {*} dataSource + * @returns + */ + setViewportDisplaySets( + viewportIndex: number, + viewportData: StackData | VolumeData, + publicViewportOptions: PublicViewportOptions, + publicDisplaySetOptions: DisplaySetOptions[] + ): void; +} diff --git a/extensions/cornerstone/src/services/ViewportService/Viewport.ts b/extensions/cornerstone/src/services/ViewportService/Viewport.ts new file mode 100644 index 00000000000..4767242582a --- /dev/null +++ b/extensions/cornerstone/src/services/ViewportService/Viewport.ts @@ -0,0 +1,221 @@ +import { Types, Enums, CONSTANTS } from '@cornerstonejs/core'; +import getCornerstoneBlendMode from '../../utils/getCornerstoneBlendMode'; +import getCornerstoneOrientation from '../../utils/getCornerstoneOrientation'; +import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType'; +import JumpPresets from '../../utils/JumpPresets'; +import { SyncGroup } from '../SyncGroupService/SyncGroupService'; + +export type InitialImageOptions = { + index?: number; + preset?: JumpPresets; +}; + +export type ViewportOptions = { + viewportType: Enums.ViewportType; + toolGroupId: string; + viewportId: string; + orientation?: Types.Orientation; + background?: Types.Point3; + initialView?: string; + syncGroups?: SyncGroup[]; + initialImageOptions?: InitialImageOptions; + customViewportOptions?: Record; +}; + +export type PublicViewportOptions = { + viewportType?: string; + toolGroupId?: string; + viewportId?: string; + orientation?: string; + background?: Types.Point3; + initialView?: string; + syncGroups?: SyncGroup[]; + initialImageOptions?: InitialImageOptions; + customViewportOptions?: Record; +}; + +export type PublicDisplaySetOptions = { + voi?: VOI; + voiInverted?: boolean; + blendMode?: string; + slabThickness?: number; + colormap?: string; +}; + +export type DisplaySetOptions = { + voi?: VOI; + voiInverted: boolean; + blendMode?: Enums.BlendModes; + slabThickness?: number; + colormap?: string; +}; + +type VOI = { + windowWidth: number; + windowCenter: number; +}; + +export type DisplaySet = { + displaySetInstanceUID: string; +}; + +const STACK = 'stack'; +const VOLUME = 'volume'; +const DEFAULT_TOOLGROUP_ID = 'default'; + +class ViewportInfo { + private viewportId = ''; + private viewportIndex: number; + private element: HTMLDivElement; + private viewportOptions: ViewportOptions; + private displaySetOptions: Array; + private renderingEngineId: string; + + constructor(viewportIndex: number, viewportId: string) { + this.viewportIndex = viewportIndex; + this.viewportId = viewportId; + this.setPublicViewportOptions({}); + this.setPublicDisplaySetOptions([{}]); + } + + public setRenderingEngineId(renderingEngineId: string): void { + this.renderingEngineId = renderingEngineId; + } + + public getRenderingEngineId(): string { + return this.renderingEngineId; + } + + public setViewportId(viewportId: string): void { + this.viewportId = viewportId; + } + public setViewportIndex(viewportIndex: number): void { + this.viewportIndex = viewportIndex; + } + + public setElement(element: HTMLDivElement): void { + this.element = element; + } + + public getViewportIndex(): number { + return this.viewportIndex; + } + + public getElement(): HTMLDivElement { + return this.element; + } + + public getViewportId(): string { + return this.viewportId; + } + + public setPublicDisplaySetOptions( + publicDisplaySetOptions: Array + ): void { + // map the displaySetOptions and check if they are undefined then set them to default values + const displaySetOptions = this.mapDisplaySetOptions( + publicDisplaySetOptions + ); + + this.setDisplaySetOptions(displaySetOptions); + } + + public setPublicViewportOptions( + viewportOptionsEntry: PublicViewportOptions + ): void { + let viewportType = viewportOptionsEntry.viewportType; + let toolGroupId = viewportOptionsEntry.toolGroupId; + let orientation; + + if (!viewportType) { + viewportType = getCornerstoneViewportType(STACK); + } else { + viewportType = getCornerstoneViewportType( + viewportOptionsEntry.viewportType + ); + } + + // map SAGITTAL, AXIAL, CORONAL orientation to be used by cornerstone + if (viewportOptionsEntry.viewportType?.toLowerCase() === VOLUME) { + orientation = getCornerstoneOrientation(viewportOptionsEntry.orientation); + } else { + orientation = CONSTANTS.ORIENTATION.AXIAL; + } + + if (!toolGroupId) { + toolGroupId = DEFAULT_TOOLGROUP_ID; + } + + this.setViewportOptions({ + ...viewportOptionsEntry, + viewportId: this.viewportId, + viewportType: viewportType as Enums.ViewportType, + orientation, + toolGroupId, + }); + } + + public setViewportOptions(viewportOptions: ViewportOptions): void { + this.viewportOptions = viewportOptions; + } + + public getViewportOptions(): ViewportOptions { + return this.viewportOptions; + } + + public setDisplaySetOptions( + displaySetOptions: Array + ): void { + this.displaySetOptions = displaySetOptions; + } + + public getSyncGroups(): SyncGroup[] { + return this.viewportOptions.syncGroups || []; + } + + public getDisplaySetOptions(): Array { + return this.displaySetOptions; + } + + public getViewportType(): Enums.ViewportType { + return this.viewportOptions.viewportType || Enums.ViewportType.STACK; + } + + public getToolGroupId(): string { + return this.viewportOptions.toolGroupId; + } + + public getBackground(): Types.Point3 { + return this.viewportOptions.background || [0, 0, 0]; + } + + public getOrientation(): Types.Orientation { + return this.viewportOptions.orientation; + } + + public getInitialImageOptions(): InitialImageOptions { + return this.viewportOptions.initialImageOptions; + } + + private mapDisplaySetOptions( + publicDisplaySetOptions: Array + ): Array { + const displaySetOptions: Array = []; + + publicDisplaySetOptions.forEach(option => { + const blendMode = getCornerstoneBlendMode(option.blendMode); + + displaySetOptions.push({ + voi: option.voi || ({} as VOI), + voiInverted: option.voiInverted || false, + colormap: option.colormap || undefined, + slabThickness: option.slabThickness, + blendMode, + }); + }); + + return displaySetOptions; + } +} + +export default ViewportInfo; diff --git a/extensions/cornerstone/src/services/ViewportService/constants.ts b/extensions/cornerstone/src/services/ViewportService/constants.ts new file mode 100644 index 00000000000..3945c323c5d --- /dev/null +++ b/extensions/cornerstone/src/services/ViewportService/constants.ts @@ -0,0 +1,3 @@ +const RENDERING_ENGINE_ID = 'OHIFCornerstoneRenderingEngine'; + +export { RENDERING_ENGINE_ID }; diff --git a/extensions/cornerstone/src/state.js b/extensions/cornerstone/src/state.ts similarity index 74% rename from extensions/cornerstone/src/state.js rename to extensions/cornerstone/src/state.ts index e582b4bd5a8..6c6aa3d7d01 100644 --- a/extensions/cornerstone/src/state.js +++ b/extensions/cornerstone/src/state.ts @@ -1,6 +1,6 @@ const state = { // The `defaultContext` of an extension's commandsModule - DEFAULT_CONTEXT: 'ACTIVE_VIEWPORT::CORNERSTONE', + DEFAULT_CONTEXT: 'CORNERSTONE', enabledElements: {}, }; @@ -9,7 +9,11 @@ const state = { * @param {HTMLElement} dom Active viewport element. * @return void */ -const setEnabledElement = (viewportIndex, element, context) => { +const setEnabledElement = ( + viewportIndex: number, + element: HTMLElement, + context?: string +): void => { const targetContext = context || state.DEFAULT_CONTEXT; state.enabledElements[viewportIndex] = { @@ -19,7 +23,7 @@ const setEnabledElement = (viewportIndex, element, context) => { }; /** - * Grabs the enabled element `dom` reference of an adective viewport. + * Grabs the enabled element `dom` reference of an ative viewport. * * @return {HTMLElement} Active viewport element. */ diff --git a/extensions/cornerstone/src/toolbarModule.js b/extensions/cornerstone/src/toolbarModule.js deleted file mode 100644 index 40ba576ebb0..00000000000 --- a/extensions/cornerstone/src/toolbarModule.js +++ /dev/null @@ -1,123 +0,0 @@ -// Visible? -// Disabled? -// Based on contexts or misc. criteria? -// -- ACTIVE_ROUTE::VIEWER -// -- ACTIVE_VIEWPORT::CORNERSTONE -// setToolActive commands should receive the button event that triggered -// so we can do the "bind to this button" magic - -// const definitions = [ -// // OLD -// { -// id: 'StackScroll', -// label: 'Stack Scroll', -// icon: 'bars', -// // -// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, -// commandName: 'setToolActive', -// commandOptions: { toolName: 'StackScroll' }, -// }, -// { -// id: 'Reset', -// label: 'Reset', -// icon: 'reset', -// // -// type: TOOLBAR_BUTTON_TYPES.COMMAND, -// commandName: 'resetViewport', -// }, -// { -// id: 'Cine', -// label: 'CINE', -// icon: 'youtube', -// // -// type: TOOLBAR_BUTTON_TYPES.BUILT_IN, -// options: { -// behavior: TOOLBAR_BUTTON_BEHAVIORS.CINE, -// }, -// }, -// { -// id: 'More', -// label: 'More', -// icon: 'ellipse-circle', -// buttons: [ -// { -// id: 'Magnify', -// label: 'Magnify', -// icon: 'circle', -// // -// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, -// commandName: 'setToolActive', -// commandOptions: { toolName: 'Magnify' }, -// }, -// { -// id: 'WwwcRegion', -// label: 'ROI Window', -// icon: 'stop', -// // -// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, -// commandName: 'setToolActive', -// commandOptions: { toolName: 'WwwcRegion' }, -// }, -// { -// id: 'Invert', -// label: 'Invert', -// icon: 'adjust', -// // -// type: TOOLBAR_BUTTON_TYPES.COMMAND, -// commandName: 'invertViewport', -// }, -// { -// id: 'RotateRight', -// label: 'Rotate Right', -// icon: 'rotate-right', -// // -// type: TOOLBAR_BUTTON_TYPES.COMMAND, -// commandName: 'rotateViewportCW', -// }, -// { -// id: 'FlipH', -// label: 'Flip H', -// icon: 'ellipse-h', -// // -// type: TOOLBAR_BUTTON_TYPES.COMMAND, -// commandName: 'flipViewportHorizontal', -// }, -// { -// id: 'FlipV', -// label: 'Flip V', -// icon: 'ellipse-v', -// // -// type: TOOLBAR_BUTTON_TYPES.COMMAND, -// commandName: 'flipViewportVertical', -// }, -// { -// id: 'Clear', -// label: 'Clear', -// icon: 'trash', -// // -// type: TOOLBAR_BUTTON_TYPES.COMMAND, -// commandName: 'clearAnnotations', -// }, -// { -// id: 'Eraser', -// label: 'Eraser', -// icon: 'eraser', -// // -// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE, -// commandName: 'setToolActive', -// commandOptions: { toolName: 'Eraser' }, -// }, -// ], -// }, -// { -// id: 'Exit2DMPR', -// label: 'Exit 2D MPR', -// icon: 'times', -// // -// type: TOOLBAR_BUTTON_TYPES.COMMAND, -// commandName: 'setCornerstoneLayout', -// context: 'ACTIVE_VIEWPORT::VTK', -// }, -// ]; - -export default []; diff --git a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx new file mode 100644 index 00000000000..f29aa7c4a3d --- /dev/null +++ b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx @@ -0,0 +1,233 @@ +import React from 'react'; +import domtoimage from 'dom-to-image'; + +import { + Enums, + getEnabledElement, + getOrCreateCanvas, + StackViewport, +} from '@cornerstonejs/core'; +import { ToolGroupManager } from '@cornerstonejs/tools'; +import PropTypes from 'prop-types'; +import { ViewportDownloadForm } from '@ohif/ui'; + +import { getEnabledElement as OHIFgetEnabledElement } from '../state'; + +const MINIMUM_SIZE = 100; +const DEFAULT_SIZE = 512; +const MAX_TEXTURE_SIZE = 10000; +const VIEWPORT_ID = 'cornerstone-viewport-download-form'; +const TOOLGROUP_ID = 'cornerstone-viewport-download-form-toolgroup'; + +const CornerstoneViewportDownloadForm = ({ + onClose, + activeViewportIndex, + CornerstoneViewportService, +}) => { + const enabledElement = OHIFgetEnabledElement(activeViewportIndex); + const activeViewportElement = enabledElement?.element; + + const enableViewport = viewportElement => { + if (viewportElement) { + const renderingEngine = CornerstoneViewportService.getRenderingEngine(); + + const viewportInput = { + viewportId: VIEWPORT_ID, + element: viewportElement, + type: Enums.ViewportType.STACK, + }; + + renderingEngine.enableElement(viewportInput); + } + }; + + const disableViewport = viewportElement => { + if (viewportElement) { + const renderingEngine = CornerstoneViewportService.getRenderingEngine(); + + return new Promise(resolve => { + renderingEngine.disableElement(VIEWPORT_ID); + ToolGroupManager.destroyToolGroup(TOOLGROUP_ID); + }); + } + }; + + const updateViewportPreview = ( + downloadViewportElement, + internalCanvas, + fileType + ) => + new Promise(resolve => { + const enabledElement = getEnabledElement(downloadViewportElement); + + const { viewport: downloadViewport, renderingEngine } = enabledElement; + + // Note: Since any trigger of dimensions will update the viewport, + // we need to resize the offScreenCanvas to accommodate for the new + // dimensions, this is due to the reason that we are using the GPU offScreenCanvas + // to render the viewport for the downloadViewport. + renderingEngine.resize(); + + // Trigger the render on the viewport to update the on screen + downloadViewport.render(); + + downloadViewportElement.addEventListener( + Enums.Events.IMAGE_RENDERED, + function updateViewport(event) { + const enabledElement = getEnabledElement(event.target); + const { viewport } = enabledElement; + const { element } = viewport; + + const downloadCanvas = getOrCreateCanvas(element); + + const type = 'image/' + fileType; + const dataUrl = downloadCanvas.toDataURL(type, 1); + + let newWidth = element.offsetHeight; + let newHeight = element.offsetWidth; + + if (newWidth > DEFAULT_SIZE || newHeight > DEFAULT_SIZE) { + const multiplier = DEFAULT_SIZE / Math.max(newWidth, newHeight); + newHeight *= multiplier; + newWidth *= multiplier; + } + + resolve({ dataUrl, width: newWidth, height: newHeight }); + + downloadViewportElement.removeEventListener( + Enums.Events.IMAGE_RENDERED, + updateViewport + ); + } + ); + }); + + const loadImage = (activeViewportElement, viewportElement, width, height) => + new Promise(resolve => { + if (activeViewportElement && viewportElement) { + const activeViewportEnabledElement = getEnabledElement( + activeViewportElement + ); + + if (!activeViewportEnabledElement) { + return; + } + + const { viewport } = activeViewportEnabledElement; + + if (!(viewport instanceof StackViewport)) { + throw new Error('Viewport is not a StackViewport'); + } + + const imageId = viewport.getCurrentImageId(); + + const renderingEngine = CornerstoneViewportService.getRenderingEngine(); + const downloadViewport = renderingEngine.getViewport( + VIEWPORT_ID + ) as StackViewport; + + downloadViewport.setStack([imageId]).then(() => { + const properties = viewport.getProperties(); + downloadViewport.setProperties(properties); + + const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE); + const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE); + + resolve({ width: newWidth, height: newHeight }); + }); + } + }); + + const toggleAnnotations = ( + toggle, + viewportElement, + activeViewportElement + ) => { + const activeViewportEnabledElement = getEnabledElement( + activeViewportElement + ); + + const downloadViewportElement = getEnabledElement(viewportElement); + + const { + viewportId: activeViewportId, + renderingEngineId, + } = activeViewportEnabledElement; + const { viewportId: downloadViewportId } = downloadViewportElement; + + if (!activeViewportEnabledElement || !downloadViewportElement) { + return; + } + + const toolGroup = ToolGroupManager.getToolGroupForViewport( + activeViewportId, + renderingEngineId + ); + + let downloadToolGroup = ToolGroupManager.getToolGroupForViewport( + downloadViewportId, + renderingEngineId + ); + + if (downloadToolGroup === undefined) { + downloadToolGroup = ToolGroupManager.createToolGroup(TOOLGROUP_ID); + + // what tools were in the active viewport? + // make them all enabled instances so that they can not be interacted + // with in the download viewport + Object.values(toolGroup._toolInstances).forEach(tool => { + downloadToolGroup.addTool(tool.getToolName()); + }); + + // add the viewport to the toolGroup + downloadToolGroup.addViewport(downloadViewportId); + } + + Object.values(downloadToolGroup._toolInstances).forEach(tool => { + const toolName = tool.getToolName(); + if (toggle) { + downloadToolGroup.setToolEnabled(toolName); + } else { + downloadToolGroup.setToolDisabled(toolName); + } + }); + }; + + const downloadBlob = (filename, fileType) => { + const file = `${filename}.${fileType}`; + const divForDownloadViewport = document.querySelector( + `div[data-viewport-uid="${VIEWPORT_ID}"]` + ); + + domtoimage.toPng(divForDownloadViewport).then(dataUrl => { + const link = document.createElement('a'); + link.download = file; + link.href = dataUrl; + link.click(); + }); + }; + + return ( + + ); +}; + +CornerstoneViewportDownloadForm.propTypes = { + onClose: PropTypes.func, + activeViewportIndex: PropTypes.number.isRequired, +}; + +export default CornerstoneViewportDownloadForm; diff --git a/extensions/cornerstone/src/utils/JumpPresets.ts b/extensions/cornerstone/src/utils/JumpPresets.ts new file mode 100644 index 00000000000..e9417deff47 --- /dev/null +++ b/extensions/cornerstone/src/utils/JumpPresets.ts @@ -0,0 +1,14 @@ +/** + * Jump Presets - This enum defines the 3 jump states which are available + * to be used with the jumpToSlice utility function. + */ +enum JumpPresets { + /** Jumps to first slice */ + First = 'first', + /** Jumps to last slice */ + Last = 'last', + /** Jumps to the middle slice */ + Middle = 'middle', +} + +export default JumpPresets; diff --git a/extensions/cornerstone/src/callInputDialog.js b/extensions/cornerstone/src/utils/callInputDialog.tsx similarity index 100% rename from extensions/cornerstone/src/callInputDialog.js rename to extensions/cornerstone/src/utils/callInputDialog.tsx diff --git a/extensions/cornerstone/src/utils/colormap/applyPreset.js b/extensions/cornerstone/src/utils/colormap/applyPreset.js new file mode 100644 index 00000000000..087ce48579b --- /dev/null +++ b/extensions/cornerstone/src/utils/colormap/applyPreset.js @@ -0,0 +1,139 @@ +import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'; +import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'; + +import presets from './presets'; + +// get preset from preset name +function getPreset(name) { + return presets.find(preset => preset.name === name); +} + +// Todo: we should be able to register a new preset via the utilityModule +// of the cornerstone extension +export default function applyPreset(actor, presetName) { + const preset = getPreset(presetName); + // Create color transfer function + const colorTransferArray = preset.colorTransfer + .split(' ') + .splice(1) + .map(parseFloat); + + const { shiftRange } = getShiftRange(colorTransferArray); + let min = shiftRange[0]; + const width = shiftRange[1] - shiftRange[0]; + const cfun = vtkColorTransferFunction.newInstance(); + const normColorTransferValuePoints = []; + for (let i = 0; i < colorTransferArray.length; i += 4) { + let value = colorTransferArray[i]; + const r = colorTransferArray[i + 1]; + const g = colorTransferArray[i + 2]; + const b = colorTransferArray[i + 3]; + + value = (value - min) / width; + normColorTransferValuePoints.push([value, r, g, b]); + } + + applyPointsToRGBFunction(normColorTransferValuePoints, shiftRange, cfun); + + actor.getProperty().setRGBTransferFunction(0, cfun); + + // Create scalar opacity function + const scalarOpacityArray = preset.scalarOpacity + .split(' ') + .splice(1) + .map(parseFloat); + + const ofun = vtkPiecewiseFunction.newInstance(); + const normPoints = []; + for (let i = 0; i < scalarOpacityArray.length; i += 2) { + let value = scalarOpacityArray[i]; + const opacity = scalarOpacityArray[i + 1]; + + value = (value - min) / width; + + normPoints.push([value, opacity]); + } + + applyPointsToPiecewiseFunction(normPoints, shiftRange, ofun); + + actor.getProperty().setScalarOpacity(0, ofun); + + const [ + gradientMinValue, + gradientMinOpacity, + gradientMaxValue, + gradientMaxOpacity, + ] = preset.gradientOpacity + .split(' ') + .splice(1) + .map(parseFloat); + + actor.getProperty().setUseGradientOpacity(0, true); + actor.getProperty().setGradientOpacityMinimumValue(0, gradientMinValue); + actor.getProperty().setGradientOpacityMinimumOpacity(0, gradientMinOpacity); + actor.getProperty().setGradientOpacityMaximumValue(0, gradientMaxValue); + actor.getProperty().setGradientOpacityMaximumOpacity(0, gradientMaxOpacity); + + if (preset.interpolation === '1') { + actor.getProperty().setInterpolationTypeToFastLinear(); + //actor.getProperty().setInterpolationTypeToLinear() + } + + const ambient = parseFloat(preset.ambient); + //const shade = preset.shade === '1' + const diffuse = parseFloat(preset.diffuse); + const specular = parseFloat(preset.specular); + const specularPower = parseFloat(preset.specularPower); + + //actor.getProperty().setShade(shade) + actor.getProperty().setAmbient(ambient); + actor.getProperty().setDiffuse(diffuse); + actor.getProperty().setSpecular(specular); + actor.getProperty().setSpecularPower(specularPower); +} + +function getShiftRange(colorTransferArray) { + // Credit to paraview-glance + // https://github.com/Kitware/paraview-glance/blob/3fec8eeff31e9c19ad5b6bff8e7159bd745e2ba9/src/components/controls/ColorBy/script.js#L133 + + // shift range is original rgb/opacity range centered around 0 + let min = Infinity; + let max = -Infinity; + for (let i = 0; i < colorTransferArray.length; i += 4) { + min = Math.min(min, colorTransferArray[i]); + max = Math.max(max, colorTransferArray[i]); + } + + const center = (max - min) / 2; + + return { + shiftRange: [-center, center], + min, + max, + }; +} + +function applyPointsToRGBFunction(points, range, cfun) { + const width = range[1] - range[0]; + const rescaled = points.map(([x, r, g, b]) => [ + x * width + range[0], + r, + g, + b, + ]); + + cfun.removeAllPoints(); + rescaled.forEach(([x, r, g, b]) => cfun.addRGBPoint(x, r, g, b)); + + return rescaled; +} + +function applyPointsToPiecewiseFunction(points, range, pwf) { + const width = range[1] - range[0]; + const rescaled = points.map(([x, y]) => [x * width + range[0], y]); + + pwf.removeAllPoints(); + rescaled.forEach(([x, y]) => pwf.addPoint(x, y)); + + return rescaled; +} diff --git a/extensions/cornerstone/src/utils/colormap/colors.js b/extensions/cornerstone/src/utils/colormap/colors.js new file mode 100644 index 00000000000..2cc80a9a2c1 --- /dev/null +++ b/extensions/cornerstone/src/utils/colormap/colors.js @@ -0,0 +1,29 @@ +// https://www.slicer.org/w/index.php/Slicer3:2010_GenericAnatomyColors#Lookup_table +const colors = [ + { + integerLabel: 0, + textLabel: 'background', + color: [0, 0, 0, 0], + }, + { + integerLabel: 1, + textLabel: 'tissue', + // color: [255, 174, 128, 255], + color: [255, 0, 0, 255], + }, + { + integerLabel: 2, + textLabel: 'bone', + // color: [241, 214, 145, 255], + color: [0, 255, 0, 255], + }, + { + integerLabel: 3, + textLabel: 'skin', + // color: [177, 122, 101, 255], + color: [0, 0, 255, 255], + }, + // .... +]; + +export default colors; diff --git a/extensions/cornerstone/src/utils/colormap/presets.js b/extensions/cornerstone/src/utils/colormap/presets.js new file mode 100644 index 00000000000..4db2da8e362 --- /dev/null +++ b/extensions/cornerstone/src/utils/colormap/presets.js @@ -0,0 +1,435 @@ +const presets = [ + { + name: 'CT-AAA', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '12 -3024 0 143.556 0 166.222 0.686275 214.389 0.696078 419.736 0.833333 3071 0.803922', + id: 'vtkMRMLVolumePropertyNode1', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '24 -3024 0 0 0 143.556 0.615686 0.356863 0.184314 166.222 0.882353 0.603922 0.290196 214.389 1 1 1 419.736 1 0.937033 0.954531 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '143.556 419.736', + }, + { + name: 'CT-AAA2', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '16 -3024 0 129.542 0 145.244 0.166667 157.02 0.5 169.918 0.627451 395.575 0.8125 1578.73 0.8125 3071 0.8125', + id: 'vtkMRMLVolumePropertyNode2', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '32 -3024 0 0 0 129.542 0.54902 0.25098 0.14902 145.244 0.6 0.627451 0.843137 157.02 0.890196 0.47451 0.6 169.918 0.992157 0.870588 0.392157 395.575 1 0.886275 0.658824 1578.73 1 0.829256 0.957922 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '0 1600', + }, + { + name: 'CT-Bone', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: '8 -3024 0 -16.4458 0 641.385 0.715686 3071 0.705882', + id: 'vtkMRMLVolumePropertyNode3', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '16 -3024 0 0 0 -16.4458 0.729412 0.254902 0.301961 641.385 0.905882 0.815686 0.552941 3071 1 1 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '-16.4458 641.385', + }, + { + name: 'CT-Bones', + gradientOpacity: '4 0 1 985.12 1', + specularPower: '1', + scalarOpacity: '8 -1000 0 152.19 0 278.93 0.190476 952 0.2', + id: 'vtkMRMLVolumePropertyNode4', + specular: '0', + shade: '1', + ambient: '0.2', + colorTransfer: + '20 -1000 0.3 0.3 1 -488 0.3 1 0.3 463.28 1 0 0 659.15 1 0.912535 0.0374849 953 1 0.3 0.3', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '152.19 952', + }, + { + name: 'CT-Cardiac', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '12 -3024 0 -77.6875 0 94.9518 0.285714 179.052 0.553571 260.439 0.848214 3071 0.875', + id: 'vtkMRMLVolumePropertyNode5', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '24 -3024 0 0 0 -77.6875 0.54902 0.25098 0.14902 94.9518 0.882353 0.603922 0.290196 179.052 1 0.937033 0.954531 260.439 0.615686 0 0 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '-77.6875 260.439', + }, + { + name: 'CT-Cardiac2', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '12 -3024 0 42.8964 0 163.488 0.428571 277.642 0.776786 1587 0.754902 3071 0.754902', + id: 'vtkMRMLVolumePropertyNode6', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '24 -3024 0 0 0 42.8964 0.54902 0.25098 0.14902 163.488 0.917647 0.639216 0.0588235 277.642 1 0.878431 0.623529 1587 1 1 1 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '42.8964 1587', + }, + { + name: 'CT-Cardiac3', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '14 -3024 0 -86.9767 0 45.3791 0.169643 139.919 0.589286 347.907 0.607143 1224.16 0.607143 3071 0.616071', + id: 'vtkMRMLVolumePropertyNode7', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '28 -3024 0 0 0 -86.9767 0 0.25098 1 45.3791 1 0 0 139.919 1 0.894893 0.894893 347.907 1 1 0.25098 1224.16 1 1 1 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '-86.9767 1224.16', + }, + { + name: 'CT-Chest-Contrast-Enhanced', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '10 -3024 0 67.0106 0 251.105 0.446429 439.291 0.625 3071 0.616071', + id: 'vtkMRMLVolumePropertyNode8', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '20 -3024 0 0 0 67.0106 0.54902 0.25098 0.14902 251.105 0.882353 0.603922 0.290196 439.291 1 0.937033 0.954531 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '67.0106 439.291', + }, + { + name: 'CT-Chest-Vessels', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: + '10 -3024 0 -1278.35 0 22.8277 0.428571 439.291 0.625 3071 0.616071', + id: 'vtkMRMLVolumePropertyNode9', + specular: '0', + shade: '1', + ambient: '0.2', + colorTransfer: + '20 -3024 0 0 0 -1278.35 0.54902 0.25098 0.14902 22.8277 0.882353 0.603922 0.290196 439.291 1 0.937033 0.954531 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '-1278.35 439.291', + }, + { + name: 'CT-Coronary-Arteries', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: + '12 -2048 0 136.47 0 159.215 0.258929 318.43 0.571429 478.693 0.776786 3661 1', + id: 'vtkMRMLVolumePropertyNode10', + specular: '0', + shade: '0', + ambient: '0.2', + colorTransfer: + '24 -2048 0 0 0 136.47 0 0 0 159.215 0.159804 0.159804 0.159804 318.43 0.764706 0.764706 0.764706 478.693 1 1 1 3661 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '136.47 478.693', + }, + { + name: 'CT-Coronary-Arteries-2', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: + '14 -2048 0 142.677 0 145.016 0.116071 192.174 0.5625 217.24 0.776786 384.347 0.830357 3661 0.830357', + id: 'vtkMRMLVolumePropertyNode11', + specular: '0', + shade: '1', + ambient: '0.2', + colorTransfer: + '28 -2048 0 0 0 142.677 0 0 0 145.016 0.615686 0 0.0156863 192.174 0.909804 0.454902 0 217.24 0.972549 0.807843 0.611765 384.347 0.909804 0.909804 1 3661 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '142.677 384.347', + }, + { + name: 'CT-Coronary-Arteries-3', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '14 -2048 0 128.643 0 129.982 0.0982143 173.636 0.669643 255.884 0.857143 584.878 0.866071 3661 1', + id: 'vtkMRMLVolumePropertyNode12', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '28 -2048 0 0 0 128.643 0 0 0 129.982 0.615686 0 0.0156863 173.636 0.909804 0.454902 0 255.884 0.886275 0.886275 0.886275 584.878 0.968627 0.968627 0.968627 3661 1 1 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '128.643 584.878', + }, + { + name: 'CT-Cropped-Volume-Bone', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: '10 -2048 0 -451 0 -450 1 1050 1 3661 1', + id: 'vtkMRMLVolumePropertyNode13', + specular: '0', + shade: '0', + ambient: '0.2', + colorTransfer: + '20 -2048 0 0 0 -451 0 0 0 -450 0.0556356 0.0556356 0.0556356 1050 1 1 1 3661 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '-451 1050', + }, + { + name: 'CT-Fat', + gradientOpacity: '6 0 1 985.12 1 988 1', + specularPower: '1', + scalarOpacity: '14 -1000 0 -100 0 -99 0.15 -60 0.15 -59 0 101.2 0 952 0', + id: 'vtkMRMLVolumePropertyNode14', + specular: '0', + references: '0', + shade: '0', + ambient: '0.2', + colorTransfer: + '36 -1000 0.3 0.3 1 -497.5 0.3 1 0.3 -99 0 0 1 -76.946 0 1 0 -65.481 0.835431 0.888889 0.0165387 83.89 1 0 0 463.28 1 0 0 659.15 1 0.912535 0.0374849 2952 1 0.300267 0.299886', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '-100 101.2', + }, + { + name: 'CT-Liver-Vasculature', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: + '14 -2048 0 149.113 0 157.884 0.482143 339.96 0.660714 388.526 0.830357 1197.95 0.839286 3661 0.848214', + id: 'vtkMRMLVolumePropertyNode15', + specular: '0', + shade: '0', + ambient: '0.2', + colorTransfer: + '28 -2048 0 0 0 149.113 0 0 0 157.884 0.501961 0.25098 0 339.96 0.695386 0.59603 0.36886 388.526 0.854902 0.85098 0.827451 1197.95 1 1 1 3661 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '149.113 1197.95', + }, + { + name: 'CT-Lung', + gradientOpacity: '6 0 1 985.12 1 988 1', + specularPower: '1', + scalarOpacity: '12 -1000 0 -600 0 -599 0.15 -400 0.15 -399 0 2952 0', + id: 'vtkMRMLVolumePropertyNode16', + specular: '0', + references: '0', + shade: '1', + ambient: '0.2', + colorTransfer: + '24 -1000 0.3 0.3 1 -600 0 0 1 -530 0.134704 0.781726 0.0724558 -460 0.929244 1 0.109473 -400 0.888889 0.254949 0.0240258 2952 1 0.3 0.3', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '-600 -399', + }, + { + name: 'CT-MIP', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: '8 -3024 0 -637.62 0 700 1 3071 1', + id: 'vtkMRMLVolumePropertyNode17', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: '16 -3024 0 0 0 -637.62 1 1 1 700 1 1 1 3071 1 1 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '-637.62 700', + }, + { + name: 'CT-Muscle', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: + '10 -3024 0 -155.407 0 217.641 0.676471 419.736 0.833333 3071 0.803922', + id: 'vtkMRMLVolumePropertyNode18', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '20 -3024 0 0 0 -155.407 0.54902 0.25098 0.14902 217.641 0.882353 0.603922 0.290196 419.736 1 0.937033 0.954531 3071 0.827451 0.658824 1', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '-155.407 419.736', + }, + { + name: 'CT-Pulmonary-Arteries', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: + '14 -2048 0 -568.625 0 -364.081 0.0714286 -244.813 0.401786 18.2775 0.607143 447.798 0.830357 3592.73 0.839286', + id: 'vtkMRMLVolumePropertyNode19', + specular: '0', + shade: '1', + ambient: '0.2', + colorTransfer: + '28 -2048 0 0 0 -568.625 0 0 0 -364.081 0.396078 0.301961 0.180392 -244.813 0.611765 0.352941 0.0705882 18.2775 0.843137 0.0156863 0.156863 447.798 0.752941 0.752941 0.752941 3592.73 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '-568.625 447.798', + }, + { + name: 'CT-Soft-Tissue', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: '10 -2048 0 -167.01 0 -160 1 240 1 3661 1', + id: 'vtkMRMLVolumePropertyNode20', + specular: '0', + shade: '0', + ambient: '0.2', + colorTransfer: + '20 -2048 0 0 0 -167.01 0 0 0 -160 0.0556356 0.0556356 0.0556356 240 1 1 1 3661 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '-167.01 240', + }, + { + name: 'CT-Air', + gradientOpacity: '4 0 1 255 1', + specularPower: '10', + scalarOpacity: '8 -3024 0.705882 -900.0 0.715686 -500.0 0 3071 0', + id: 'vtkMRMLVolumePropertyNode21', + specular: '0.2', + shade: '1', + ambient: '0.1', + colorTransfer: + '16 -3024 1 1 1 -900.0 0.2 1.0 1.0 -500.0 0.3 0.3 1.0 3071 0 0 0 ', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '-1200.0 -200.0', + }, + { + name: 'MR-Angio', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: + '12 -2048 0 151.354 0 158.279 0.4375 190.112 0.580357 200.873 0.732143 3661 0.741071', + id: 'vtkMRMLVolumePropertyNode22', + specular: '0', + shade: '1', + ambient: '0.2', + colorTransfer: + '24 -2048 0 0 0 151.354 0 0 0 158.279 0.74902 0.376471 0 190.112 1 0.866667 0.733333 200.873 0.937255 0.937255 0.937255 3661 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '151.354 200.873', + }, + { + name: 'MR-Default', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: '12 0 0 20 0 40 0.15 120 0.3 220 0.375 1024 0.5', + id: 'vtkMRMLVolumePropertyNode23', + specular: '0', + shade: '1', + ambient: '0.2', + colorTransfer: + '24 0 0 0 0 20 0.168627 0 0 40 0.403922 0.145098 0.0784314 120 0.780392 0.607843 0.380392 220 0.847059 0.835294 0.788235 1024 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '0 220', + }, + { + name: 'MR-MIP', + gradientOpacity: '4 0 1 255 1', + specularPower: '1', + scalarOpacity: '8 0 0 98.3725 0 416.637 1 2800 1', + id: 'vtkMRMLVolumePropertyNode24', + specular: '0', + shade: '0', + ambient: '0.2', + colorTransfer: '16 0 1 1 1 98.3725 1 1 1 416.637 1 1 1 2800 1 1 1', + selectable: 'true', + diffuse: '1', + interpolation: '1', + effectiveRange: '0 416.637', + }, + { + name: 'MR-T2-Brain', + gradientOpacity: '4 0 1 160.25 1', + specularPower: '40', + scalarOpacity: '10 0 0 36.05 0 218.302 0.171429 412.406 1 641 1', + id: 'vtkMRMLVolumePropertyNode25', + specular: '0.5', + shade: '1', + ambient: '0.3', + colorTransfer: + '16 0 0 0 0 98.7223 0.956863 0.839216 0.192157 412.406 0 0.592157 0.807843 641 1 1 1', + selectable: 'true', + diffuse: '0.6', + interpolation: '1', + effectiveRange: '0 412.406', + }, + { + name: 'DTI-FA-Brain', + gradientOpacity: '4 0 1 0.9950 1', + specularPower: '40', + scalarOpacity: + '16 0 0 0 0 0.3501 0.0158 0.49379 0.7619 0.6419 1 0.9920 1 0.9950 0 0.9950 0', + id: 'vtkMRMLVolumePropertyNode26', + specular: '0.5', + shade: '1', + ambient: '0.3', + colorTransfer: + '28 0 1 0 0 0 1 0 0 0.24974 0.4941 1 0 0.49949 0 0.9882 1 0.7492 0.51764 0 1 0.9950 1 0 0 0.9950 1 0 0', + selectable: 'true', + diffuse: '0.9', + interpolation: '1', + effectiveRange: '0 1', + }, +]; + +export default presets; diff --git a/extensions/cornerstone/src/utils/colormap/transferFunctionHelpers.js b/extensions/cornerstone/src/utils/colormap/transferFunctionHelpers.js new file mode 100644 index 00000000000..4bbc25713e8 --- /dev/null +++ b/extensions/cornerstone/src/utils/colormap/transferFunctionHelpers.js @@ -0,0 +1,93 @@ +import { cache, utilities } from '@cornerstonejs/core'; +import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps'; +import vtkColorTransferFunction from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction'; +import vtkPiecewiseFunction from '@kitware/vtk.js/Common/DataModel/PiecewiseFunction'; + +const colormaps = {}; + +export function registerColormap(colormap) { + colormaps[colormap.Name] = colormap; +} + +function setColorTransferFunctionFromVolumeMetadata({ + volumeActor, + volumeId, + inverted, +}) { + let lower, upper, windowWidth, windowCenter; + + if (volumeId) { + const volume = cache.getVolume(volumeId); + const voiLutModule = volume.metadata.voiLut[0]; + if (voiLutModule) { + windowWidth = voiLutModule.windowWidth; + windowCenter = voiLutModule.windowCenter; + } + } else { + windowWidth = 400; + windowCenter = 40; + } + + if (windowWidth == undefined || windowCenter === undefined) { + // Set to something so we can window level it manually. + lower = 200; + upper = 400; + } else { + lower = windowCenter - windowWidth / 2.0; + upper = windowCenter + windowWidth / 2.0; + } + + setLowerUpperColorTransferFunction({ volumeActor, lower, upper, inverted }); +} + +function setLowerUpperColorTransferFunction({ + volumeActor, + lower, + upper, + inverted, +}) { + volumeActor + .getProperty() + .getRGBTransferFunction(0) + .setMappingRange(lower, upper); + + if (inverted) { + utilities.invertRgbTransferFunction( + volumeActor.getProperty().getRGBTransferFunction(0) + ); + } +} + +function setColormap(volumeActor, colormap) { + const mapper = volumeActor.getMapper(); + mapper.setSampleDistance(1.0); + + const cfun = vtkColorTransferFunction.newInstance(); + + // if we have a custom colormap, use it + let preset; + if (colormaps[colormap]) { + preset = colormaps[colormap]; + } else { + preset = vtkColorMaps.getPresetByName(colormap); + } + + cfun.applyColorMap(preset); + cfun.setMappingRange(0, 5); + + volumeActor.getProperty().setRGBTransferFunction(0, cfun); + + // Create scalar opacity function + const ofun = vtkPiecewiseFunction.newInstance(); + ofun.addPoint(0, 0.0); + ofun.addPoint(0.1, 0.9); + ofun.addPoint(5, 1.0); + + volumeActor.getProperty().setScalarOpacity(0, ofun); +} + +export { + setColormap, + setColorTransferFunctionFromVolumeMetadata, + setLowerUpperColorTransferFunction, +}; diff --git a/platform/core/src/utils/dicomLoaderService.js b/extensions/cornerstone/src/utils/dicomLoaderService.js similarity index 94% rename from platform/core/src/utils/dicomLoaderService.js rename to extensions/cornerstone/src/utils/dicomLoaderService.js index f0792129a2a..dbc5b1b8310 100644 --- a/platform/core/src/utils/dicomLoaderService.js +++ b/extensions/cornerstone/src/utils/dicomLoaderService.js @@ -1,9 +1,7 @@ -import cornerstone from 'cornerstone-core'; +import { imageLoader } from '@cornerstonejs/core'; import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; import { api } from 'dicomweb-client'; -import DICOMWeb from '../DICOMWeb'; - -import errorHandler from '../errorHandler'; +import { DICOMWeb, errorHandler } from '@ohif/core'; const getImageId = imageObj => { if (!imageObj) { @@ -49,7 +47,7 @@ const fetchIt = (url, headers = DICOMWeb.getAuthorizationHeader()) => { }; const cornerstoneRetriever = imageId => { - return cornerstone.loadAndCacheImage(imageId).then(image => { + return imageLoader.loadAndCacheImage(imageId).then(image => { return image && image.data && image.data.byteArray.buffer; }); }; @@ -151,6 +149,10 @@ class DicomLoaderService { } getDicomDataMethod = fetchIt.bind(this, imageId); break; + default: + throw new Error( + `Unsupported image type: ${loaderType} for imageId: ${imageId}` + ); } return getDicomDataMethod(); diff --git a/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts b/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts new file mode 100644 index 00000000000..4bc7cb449f3 --- /dev/null +++ b/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts @@ -0,0 +1,17 @@ +import { Enums } from '@cornerstonejs/core'; + +const MIP = 'mip'; + +export default function getCornerstoneBlendMode( + blendMode: string +): Enums.BlendModes { + if (!blendMode) { + return Enums.BlendModes.COMPOSITE; + } + + if (blendMode.toLowerCase() === MIP) { + return Enums.BlendModes.MAXIMUM_INTENSITY_BLEND; + } + + throw new Error(); +} diff --git a/extensions/cornerstone/src/utils/getCornerstoneMeasurementById.js b/extensions/cornerstone/src/utils/getCornerstoneMeasurementById.js deleted file mode 100644 index 61f3003a019..00000000000 --- a/extensions/cornerstone/src/utils/getCornerstoneMeasurementById.js +++ /dev/null @@ -1,31 +0,0 @@ -import cornerstoneTools from 'cornerstone-tools'; - -const { globalImageIdSpecificToolStateManager } = cornerstoneTools; - -export default function getCornerstoneMeasurementById(id) { - const globalToolState = globalImageIdSpecificToolStateManager.saveToolState(); - - const imageIds = Object.keys(globalToolState); - - for (let i = 0; i < imageIds.length; i++) { - const imageId = imageIds[i]; - const imageIdSpecificToolState = globalToolState[imageId]; - - const toolTypes = Object.keys(imageIdSpecificToolState); - - for (let j = 0; j < toolTypes.length; j++) { - const toolType = toolTypes[j]; - const toolData = imageIdSpecificToolState[toolType].data; - - if (toolData) { - for (let k = 0; k < toolData.length; k++) { - const toolDataK = toolData[k]; - - if (toolDataK.id === id) { - return toolDataK; - } - } - } - } - } -} diff --git a/extensions/cornerstone/src/utils/getCornerstoneOrientation.ts b/extensions/cornerstone/src/utils/getCornerstoneOrientation.ts new file mode 100644 index 00000000000..ace5bcef331 --- /dev/null +++ b/extensions/cornerstone/src/utils/getCornerstoneOrientation.ts @@ -0,0 +1,22 @@ +import { CONSTANTS } from '@cornerstonejs/core'; +import { log } from '@ohif/core'; + +const AXIAL = 'axial'; +const SAGITTAL = 'sagittal'; +const CORONAL = 'coronal'; + +export default function getCornerstoneOrientation( + orientation: string +): CONSTANTS.ORIENTATION { + switch (orientation.toLowerCase()) { + case AXIAL: + return CONSTANTS.ORIENTATION.AXIAL; + case SAGITTAL: + return CONSTANTS.ORIENTATION.SAGITTAL; + case CORONAL: + return CONSTANTS.ORIENTATION.CORONAL; + default: + log.wanr('Choosing default orientation: axial'); + return CONSTANTS.ORIENTATION.AXIAL; + } +} diff --git a/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts b/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts new file mode 100644 index 00000000000..2e19312b6fd --- /dev/null +++ b/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts @@ -0,0 +1,20 @@ +import { Enums } from '@cornerstonejs/core'; + +const STACK = 'stack'; +const VOLUME = 'volume'; + +export default function getCornerstoneViewportType( + viewportType: string +): Enums.ViewportType { + if (viewportType.toLowerCase() === STACK) { + return Enums.ViewportType.STACK; + } + + if (viewportType.toLowerCase() === VOLUME) { + return Enums.ViewportType.ORTHOGRAPHIC; + } + + throw new Error( + `Invalid viewport type: ${viewportType}. Valid types are: stack, volume` + ); +} diff --git a/extensions/cornerstone/src/utils/getInterleavedFrames.js b/extensions/cornerstone/src/utils/getInterleavedFrames.js new file mode 100644 index 00000000000..d8eae131a21 --- /dev/null +++ b/extensions/cornerstone/src/utils/getInterleavedFrames.js @@ -0,0 +1,60 @@ +export default function getInterleavedFrames(imageIds) { + const minImageIdIndex = 0; + const maxImageIdIndex = imageIds.length - 1; + + const middleImageIdIndex = Math.floor(imageIds.length / 2); + + let lowerImageIdIndex = middleImageIdIndex; + let upperImageIdIndex = middleImageIdIndex; + + // Build up an array of images to prefetch, starting with the current image. + const imageIdsToPrefetch = [ + { imageId: imageIds[middleImageIdIndex], imageIdIndex: middleImageIdIndex }, + ]; + + const prefetchQueuedFilled = { + currentPositionDownToMinimum: false, + currentPositionUpToMaximum: false, + }; + + // Check if on edges and some criteria is already fulfilled + + if (middleImageIdIndex === minImageIdIndex) { + prefetchQueuedFilled.currentPositionDownToMinimum = true; + } else if (middleImageIdIndex === maxImageIdIndex) { + prefetchQueuedFilled.currentPositionUpToMaximum = true; + } + + while ( + !prefetchQueuedFilled.currentPositionDownToMinimum || + !prefetchQueuedFilled.currentPositionUpToMaximum + ) { + if (!prefetchQueuedFilled.currentPositionDownToMinimum) { + // Add imageId bellow + lowerImageIdIndex--; + imageIdsToPrefetch.push({ + imageId: imageIds[lowerImageIdIndex], + imageIdIndex: lowerImageIdIndex, + }); + + if (lowerImageIdIndex === minImageIdIndex) { + prefetchQueuedFilled.currentPositionDownToMinimum = true; + } + } + + if (!prefetchQueuedFilled.currentPositionUpToMaximum) { + // Add imageId above + upperImageIdIndex++; + imageIdsToPrefetch.push({ + imageId: imageIds[upperImageIdIndex], + imageIdIndex: upperImageIdIndex, + }); + + if (upperImageIdIndex === maxImageIdIndex) { + prefetchQueuedFilled.currentPositionUpToMaximum = true; + } + } + } + + return imageIdsToPrefetch; +} diff --git a/extensions/cornerstone/src/utils/getTools.js b/extensions/cornerstone/src/utils/getTools.js deleted file mode 100644 index c3d9b9e7740..00000000000 --- a/extensions/cornerstone/src/utils/getTools.js +++ /dev/null @@ -1,36 +0,0 @@ -import csTools from 'cornerstone-tools'; - -const toolsGroupedByType = { - touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool], - annotations: [ - csTools.ArrowAnnotateTool, - csTools.BidirectionalTool, - csTools.LengthTool, - csTools.AngleTool, - csTools.FreehandRoiTool, - csTools.EllipticalRoiTool, - csTools.DragProbeTool, - csTools.RectangleRoiTool, - ], - other: [ - csTools.PanTool, - csTools.ZoomTool, - csTools.WwwcTool, - csTools.WwwcRegionTool, - csTools.MagnifyTool, - csTools.StackScrollTool, - csTools.StackScrollMouseWheelTool, - csTools.OverlayTool, - ], -}; - -export default function getTools() { - const tools = []; - Object.keys(toolsGroupedByType).forEach(toolsGroup => - tools.push(...toolsGroupedByType[toolsGroup]) - ); - - return tools; -} - -export { toolsGroupedByType }; diff --git a/extensions/cornerstone/src/utils/interleaveCenterLoader.ts b/extensions/cornerstone/src/utils/interleaveCenterLoader.ts new file mode 100644 index 00000000000..dcf19ffd765 --- /dev/null +++ b/extensions/cornerstone/src/utils/interleaveCenterLoader.ts @@ -0,0 +1,150 @@ +import { cache, imageLoadPoolManager, Enums } from '@cornerstonejs/core'; +import getInterleavedFrames from './getInterleavedFrames'; +import { compact, flatten, zip } from 'lodash'; + +// Map of volumeId and SeriesInstanceId +const volumeIdMapsToLoad = new Map(); +const viewportIdVolumeInputArrayMap = new Map(); + +/** + * This function caches the volumeUIDs until all the volumes inside the + * hangging protocol are initialized. Then it goes through the imageIds + * of the volumes, and interleav them, in order for the volumes to be loaded + * together from middle to the start and the end. + * @param {Object} props image loading properties from Cornerstone ViewportService + * @returns + */ +export default function interleaveCenterLoader({ + data: { viewportId, volumeInputArray }, + displaySetsMatchDetails, + matchDetails, +}) { + viewportIdVolumeInputArrayMap.set(viewportId, volumeInputArray); + + // Based on the volumeInputs store the volumeIds and SeriesInstanceIds + // to keep track of the volumes being loaded + for (const volumeInput of volumeInputArray) { + const { volumeId } = volumeInput; + const volume = cache.getVolume(volumeId); + + if (!volume) { + return; + } + + // if the volumeUID is not in the volumeUIDs array, add it + if (!volumeIdMapsToLoad.has(volumeId)) { + const { metadata } = volume; + volumeIdMapsToLoad.set(volumeId, metadata.SeriesInstanceUID); + } + } + + /** + * The following is checking if all the viewports that were matched in the HP has been + * successfully created their cornerstone viewport or not. Todo: This can be + * improved by not checking it, and as soon as the matched DisplaySets have their + * volume loaded, we start the loading, but that comes at the cost of viewports + * not being created yet (e.g., in a 10 viewport ptCT fusion, when one ct viewport and one + * pt viewport are created we have a guarantee that the volumes are created in the cache + * but the rest of the viewports (fusion, mip etc.) are not created yet. So + * we can't initiate setting the volumes for those viewports. One solution can be + * to add an event when a viewport is created (not enabled element event) and then + * listen to it and as the other viewports are created we can set the volumes for them + * since volumes are already started loading. + */ + if (matchDetails.length !== viewportIdVolumeInputArrayMap.size) { + return; + } + + // Check if all the matched volumes are loaded + for (const [_, details] of displaySetsMatchDetails.entries()) { + const { SeriesInstanceUID } = details; + + // HangingProtocol has matched, but don't have all the volumes created yet, so return + if (!Array.from(volumeIdMapsToLoad.values()).includes(SeriesInstanceUID)) { + return; + } + } + + const volumeIds = Array.from(volumeIdMapsToLoad.keys()).slice(); + // get volumes from cache + const volumes = volumeIds.map(volumeId => { + return cache.getVolume(volumeId); + }); + + // iterate over all volumes, and get their imageIds, and interleave + // the imageIds and save them in AllRequests for later use + const AllRequests = []; + volumes.forEach(volume => { + const requests = volume.getImageLoadRequests(); + + if (!requests.length || !requests[0] || !requests[0].imageId) { + return; + } + + const requestImageIds = requests.map(request => { + return request.imageId; + }); + + const imageIds = getInterleavedFrames(requestImageIds); + + const reOrderedRequests = imageIds.map(({ imageId }) => { + const request = requests.find(req => req.imageId === imageId); + return request; + }); + + AllRequests.push(reOrderedRequests); + }); + + // flatten the AllRequests array, which will result in a list of all the + // imageIds for all the volumes but interleaved + const interleavedRequests = compact(flatten(zip(...AllRequests))); + + // set the finalRequests to the imageLoadPoolManager + const finalRequests = []; + interleavedRequests.forEach(request => { + const { imageId } = request; + + AllRequests.forEach(volumeRequests => { + const volumeImageIdRequest = volumeRequests.find( + req => req.imageId === imageId + ); + if (volumeImageIdRequest) { + finalRequests.push(volumeImageIdRequest); + } + }); + }); + + const requestType = Enums.RequestType.Prefetch; + const priority = 0; + + finalRequests.forEach( + ({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { + const callLoadImageBound = callLoadImage.bind( + null, + imageId, + imageIdIndex, + options + ); + + imageLoadPoolManager.addRequest( + callLoadImageBound, + requestType, + additionalDetails, + priority + ); + } + ); + + // clear the volumeIdMapsToLoad + volumeIdMapsToLoad.clear(); + + // copy the viewportIdVolumeInputArrayMap + const viewportIdVolumeInputArrayMapCopy = new Map( + viewportIdVolumeInputArrayMap + ); + + // reset the viewportIdVolumeInputArrayMap + viewportIdVolumeInputArrayMap.clear(); + + return viewportIdVolumeInputArrayMapCopy; +} diff --git a/extensions/cornerstone/src/utils/interleaveTopToBottom.ts b/extensions/cornerstone/src/utils/interleaveTopToBottom.ts new file mode 100644 index 00000000000..2c7df60bc38 --- /dev/null +++ b/extensions/cornerstone/src/utils/interleaveTopToBottom.ts @@ -0,0 +1,139 @@ +import { cache, imageLoadPoolManager, Enums } from '@cornerstonejs/core'; +import { compact, flatten, zip } from 'lodash'; + +// Map of volumeId and SeriesInstanceId +const volumeIdMapsToLoad = new Map(); +const viewportIdVolumeInputArrayMap = new Map(); + +/** + * This function caches the volumeIds until all the volumes inside the + * hanging protocol are initialized. Then it goes through the imageIds + * of the volumes, and interleave them, in order for the volumes to be loaded + * together from middle to the start and the end. + * @param {Object} {viewportData, displaySetMatchDetails} + * @returns + */ +export default function interleaveTopToBottom({ + data: { viewportId, volumeInputArray }, + displaySetsMatchDetails, + matchDetails, +}) { + viewportIdVolumeInputArrayMap.set(viewportId, volumeInputArray); + + // Based on the volumeInputs store the volumeIds and SeriesInstanceIds + // to keep track of the volumes being loaded + for (const volumeInput of volumeInputArray) { + const { volumeId } = volumeInput; + const volume = cache.getVolume(volumeId); + + if (!volume) { + return; + } + + // if the volumeUID is not in the volumeUIDs array, add it + if (!volumeIdMapsToLoad.has(volumeId)) { + const { metadata } = volume; + volumeIdMapsToLoad.set(volumeId, metadata.SeriesInstanceUID); + } + } + + /** + * The following is checking if all the viewports that were matched in the HP has been + * successfully created their cornerstone viewport or not. Todo: This can be + * improved by not checking it, and as soon as the matched DisplaySets have their + * volume loaded, we start the loading, but that comes at the cost of viewports + * not being created yet (e.g., in a 10 viewport ptCT fusion, when one ct viewport and one + * pt viewport are created we have a guarantee that the volumes are created in the cache + * but the rest of the viewports (fusion, mip etc.) are not created yet. So + * we can't initiate setting the volumes for those viewports. One solution can be + * to add an event when a viewport is created (not enabled element event) and then + * listen to it and as the other viewports are created we can set the volumes for them + * since volumes are already started loading. + */ + if (matchDetails.length !== viewportIdVolumeInputArrayMap.size) { + return; + } + + // Check if all the matched volumes are loaded + for (const [_, details] of displaySetsMatchDetails.entries()) { + const { SeriesInstanceUID } = details; + + // HangingProtocol has matched, but don't have all the volumes created yet, so return + if (!Array.from(volumeIdMapsToLoad.values()).includes(SeriesInstanceUID)) { + return; + } + } + + const volumeIds = Array.from(volumeIdMapsToLoad.keys()).slice(); + // get volumes from cache + const volumes = volumeIds.map(volumeId => { + return cache.getVolume(volumeId); + }); + + // iterate over all volumes, and get their imageIds, and interleave + // the imageIds and save them in AllRequests for later use + const AllRequests = []; + volumes.forEach(volume => { + const requests = volume.getImageLoadRequests(); + + if (!requests.length || !requests[0] || !requests[0].imageId) { + return; + } + + // reverse the requests + AllRequests.push(requests.reverse()); + }); + + // flatten the AllRequests array, which will result in a list of all the + // imageIds for all the volumes but interleaved + const interleavedRequests = compact(flatten(zip(...AllRequests))); + + // set the finalRequests to the imageLoadPoolManager + const finalRequests = []; + interleavedRequests.forEach(request => { + const { imageId } = request; + + AllRequests.forEach(volumeRequests => { + const volumeImageIdRequest = volumeRequests.find( + req => req.imageId === imageId + ); + if (volumeImageIdRequest) { + finalRequests.push(volumeImageIdRequest); + } + }); + }); + + const requestType = Enums.RequestType.Prefetch; + const priority = 0; + + finalRequests.forEach( + ({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { + const callLoadImageBound = callLoadImage.bind( + null, + imageId, + imageIdIndex, + options + ); + + imageLoadPoolManager.addRequest( + callLoadImageBound, + requestType, + additionalDetails, + priority + ); + } + ); + + // clear the volumeIdMapsToLoad + volumeIdMapsToLoad.clear(); + + // copy the viewportIdVolumeInputArrayMap + const viewportIdVolumeInputArrayMapCopy = new Map( + viewportIdVolumeInputArrayMap + ); + + // reset the viewportIdVolumeInputArrayMap + viewportIdVolumeInputArrayMap.clear(); + + return viewportIdVolumeInputArrayMapCopy; +} diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js index 89b4184f725..ded3ef07685 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.js @@ -1,57 +1,142 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; -import getPointsFromHandles from './utils/getPointsFromHandles'; -import getHandlesFromPoints from './utils/getHandlesFromPoints'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; -const ArrowAnnotate = { - toAnnotation: (measurement, definition) => {}, +const Length = { + toAnnotation: measurement => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} cornerstone Cornerstone event data + * @return {Measurement} Measurement instance + */ toMeasurement: ( - csToolsAnnotation, + csToolsEventDetail, DisplaySetService, + CornerstoneViewportService, getValueTypeFromToolType ) => { - const { element, measurementData } = csToolsAnnotation; - const tool = - csToolsAnnotation.toolType || - csToolsAnnotation.toolName || - measurementData.toolType; + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } - const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName); + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); - if (!validToolType(tool)) { + if (!validToolType) { throw new Error('Tool not supported'); } const { SOPInstanceUID, - FrameOfReferenceUID, SeriesInstanceUID, StudyInstanceUID, - } = getSOPInstanceAttributes(element); + } = getSOPInstanceAttributes( + referencedImageId, + CornerstoneViewportService, + viewportId + ); - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID + let displaySet; + + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { points } = data.handles; + + const mappedAnnotations = getMappedAnnotations( + annotation, + DisplaySetService ); - const points = []; - points.push(measurementData.handles); + const displayText = getDisplayText(mappedAnnotations, displaySet); return { - id: measurementData.id, + uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, + points, + metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, - label: measurementData.text, - description: measurementData.description, - unit: measurementData.unit, - text: measurementData.text, - type: getValueTypeFromToolType(tool), - points: getPointsFromHandles(measurementData.handles), + label: data.text, + text: data.text, + displayText: displayText, + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport: () => { + throw new Error('Not implemented'); + }, }; }, }; -export default ArrowAnnotate; +function getMappedAnnotations(annotation, DisplaySetService) { + const { metadata, data } = annotation; + const { text } = data; + const { referencedImageId } = metadata; + + const annotations = []; + + const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( + referencedImageId + ); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + + const { SeriesNumber } = displaySet; + + annotations.push({ + SeriesInstanceUID, + SOPInstanceUID, + SeriesNumber, + text, + }); + + return annotations; +} + +function getDisplayText(mappedAnnotations, displaySet) { + if (!mappedAnnotations) { + return ''; + } + + const displayText = []; + + // Area is the same for all series + const { SeriesNumber, SOPInstanceUID } = mappedAnnotations[0]; + + const instance = displaySet.images.find( + image => image.SOPInstanceUID === SOPInstanceUID + ); + + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + displayText.push( + InstanceNumber + ? `(S: ${SeriesNumber} I: ${InstanceNumber})` + : `(S: ${SeriesNumber})` + ); + + return displayText; +} + +export default Length; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js index 34eeb21ffe3..1d4a0c8244a 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.js @@ -1,59 +1,197 @@ +import { annotation } from '@cornerstonejs/tools'; + import SUPPORTED_TOOLS from './constants/supportedTools'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; -import getHandlesFromPoints from './utils/getHandlesFromPoints'; +import { utils } from '@ohif/core'; const Bidirectional = { - toAnnotation: (measurement, definition) => {}, + toAnnotation: measurement => {}, toMeasurement: ( - csToolsAnnotation, + csToolsEventDetail, DisplaySetService, + CornerstoneViewportService, getValueTypeFromToolType ) => { - const { element, measurementData } = csToolsAnnotation; - const tool = - csToolsAnnotation.toolType || - csToolsAnnotation.toolName || - measurementData.toolType; + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } - const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName); + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); - if (!validToolType(tool)) { + if (!validToolType) { throw new Error('Tool not supported'); } const { SOPInstanceUID, - FrameOfReferenceUID, SeriesInstanceUID, StudyInstanceUID, - } = getSOPInstanceAttributes(element); - - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID + } = getSOPInstanceAttributes( + referencedImageId, + CornerstoneViewportService, + viewportId ); - const { handles } = measurementData; + let displaySet; + + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { points } = data.handles; - const longAxis = [handles.start, handles.end]; - const shortAxis = [handles.perpendicularStart, handles.perpendicularEnd]; + const mappedAnnotations = getMappedAnnotations( + annotation, + DisplaySetService + ); + + const displayText = getDisplayText(mappedAnnotations, displaySet); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { - id: measurementData.id, + uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, + points, + metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, - label: measurementData.label, - description: measurementData.description, - unit: measurementData.unit, - shortestDiameter: measurementData.shortestDiameter, - longestDiameter: measurementData.longestDiameter, - type: getValueTypeFromToolType(tool), - points: { longAxis, shortAxis }, + label: data.label, + displayText: displayText, + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport, }; }, }; +function getMappedAnnotations(annotation, DisplaySetService) { + const { metadata, data } = annotation; + const { cachedStats } = data; + const { referencedImageId, referencedSeriesInstanceUID } = metadata; + const targets = Object.keys(cachedStats); + + if (!targets.length) { + return; + } + + const annotations = []; + Object.keys(cachedStats).forEach(targetId => { + const targetStats = cachedStats[targetId]; + + let displaySet; + + if (referencedImageId) { + const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( + referencedImageId + ); + + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + throw new Error( + 'Non-acquisition plane measurement mapping not supported' + ); + } + + const { SeriesNumber, SeriesInstanceUID } = displaySet; + const { length, width } = targetStats; + const unit = 'mm'; + + annotations.push({ + SeriesInstanceUID, + SeriesNumber, + unit, + length, + width, + }); + }); + + return annotations; +} + +/* +This function is used to convert the measurement data to a format that is +suitable for the report generation (e.g. for the csv report). The report +returns a list of columns and corresponding values. +*/ +function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { + const columns = []; + const values = []; + + // Add Type + columns.push('AnnotationType'); + values.push('Cornerstone:Bidirectional'); + + mappedAnnotations.forEach(annotation => { + const { length, width } = annotation; + columns.push(`Length (mm)`, `Width (mm)`); + values.push(length, width); + }); + + if (FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(FrameOfReferenceUID); + } + + if (points) { + columns.push('points'); + // points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + // convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + // so that it can be used in the csv report + values.push(points.map(p => p.join(' ')).join(';')); + } + + return { + columns, + values, + }; +} + +function getDisplayText(mappedAnnotations, displaySet) { + if (!mappedAnnotations || !mappedAnnotations.length) { + return ''; + } + + const displayText = []; + + // Area is the same for all series + const { length, width, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0]; + const roundedLength = utils.roundNumber(length, 1); + const roundedWidth = utils.roundNumber(width, 1); + + const instance = displaySet.images.find( + image => image.SOPInstanceUID === SOPInstanceUID + ); + + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + displayText.push( + InstanceNumber + ? `L: ${roundedLength} mm (S: ${SeriesNumber} I: ${InstanceNumber})` + : `L: ${roundedLength} mm (S: ${SeriesNumber})` + ); + displayText.push(`W: ${roundedWidth} mm`); + + return displayText; +} + export default Bidirectional; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js new file mode 100644 index 00000000000..149dcb644fa --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.js @@ -0,0 +1,219 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import getModalityUnit from './utils/getModalityUnit'; +import { utils } from '@ohif/core'; + +const EllipticalROI = { + toAnnotation: measurement => {}, + toMeasurement: ( + csToolsEventDetail, + DisplaySetService, + CornerstoneViewportService, + getValueTypeFromToolType + ) => { + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType) { + throw new Error('Tool not supported'); + } + + const { + SOPInstanceUID, + SeriesInstanceUID, + StudyInstanceUID, + } = getSOPInstanceAttributes( + referencedImageId, + CornerstoneViewportService, + viewportId + ); + + let displaySet; + + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { points } = data.handles; + + const mappedAnnotations = getMappedAnnotations( + annotation, + DisplaySetService + ); + + const displayText = getDisplayText(mappedAnnotations, displaySet); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID); + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + points, + metadata, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: data.label, + displayText: displayText, + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport, + }; + }, +}; + +function getMappedAnnotations(annotation, DisplaySetService) { + const { metadata, data } = annotation; + const { cachedStats } = data; + const { referencedImageId } = metadata; + const targets = Object.keys(cachedStats); + + if (!targets.length) { + return; + } + + const annotations = []; + Object.keys(cachedStats).forEach(targetId => { + const targetStats = cachedStats[targetId]; + + let displaySet; + + if (referencedImageId) { + const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( + referencedImageId + ); + + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + // Todo: Non-acquisition plane measurement mapping not supported yet + throw new Error( + 'Non-acquisition plane measurement mapping not supported' + ); + } + + const { SeriesNumber, SeriesInstanceUID } = displaySet; + const { mean, stdDev, max, area, Modality } = targetStats; + const unit = getModalityUnit(Modality); + + annotations.push({ + SeriesInstanceUID, + SeriesNumber, + Modality, + unit, + mean, + stdDev, + max, + area, + }); + }); + + return annotations; +} + +/* +This function is used to convert the measurement data to a format that is +suitable for the report generation (e.g. for the csv report). The report +returns a list of columns and corresponding values. +*/ +function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { + const columns = []; + const values = []; + + // Add Type + columns.push('AnnotationType'); + values.push('Cornerstone:EllipticalROI'); + + mappedAnnotations.forEach(annotation => { + const { mean, stdDev, max, area, unit } = annotation; + + if (!mean || !unit || !max || !area) { + return; + } + + columns.push( + `max (${unit})`, + `mean (${unit})`, + `std (${unit})`, + `area (mm2)` + ); + values.push(max, mean, stdDev, area); + }); + + if (FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(FrameOfReferenceUID); + } + + if (points) { + columns.push('points'); + // points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + // convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + // so that it can be used in the csv report + values.push(points.map(p => p.join(' ')).join(';')); + } + + return { + columns, + values, + }; +} + +function getDisplayText(mappedAnnotations, displaySet) { + if (!mappedAnnotations || !mappedAnnotations.length) { + return ''; + } + + const displayText = []; + + // Area is the same for all series + const { area, SOPInstanceUID } = mappedAnnotations[0]; + + const instance = displaySet.images.find( + image => image.SOPInstanceUID === SOPInstanceUID + ); + + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const roundedArea = utils.roundNumber(area, 2); + displayText.push(`${roundedArea} mm2`); + + // Todo: we need a better UI for displaying all these information + mappedAnnotations.forEach(mappedAnnotation => { + const { unit, max, SeriesNumber } = mappedAnnotation; + + if (max) { + const roundedMax = utils.roundNumber(max, 2); + + displayText.push( + InstanceNumber + ? `Max: ${roundedMax} ${unit} (S:${SeriesNumber} I:${InstanceNumber})` + : `Max: ${roundedMax} ${unit} (S:${SeriesNumber})` + ); + } + }); + + return displayText; +} + +export default EllipticalROI; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalRoi.js b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalRoi.js deleted file mode 100644 index b951da0dc6f..00000000000 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalRoi.js +++ /dev/null @@ -1,100 +0,0 @@ -import SUPPORTED_TOOLS from './constants/supportedTools'; -import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; -import getHandlesFromPoints from './utils/getHandlesFromPoints'; - -const EllipticalRoi = { - toAnnotation: (measurement, definition) => {}, - toMeasurement: ( - csToolsAnnotation, - DisplaySetService, - getValueTypeFromToolType - ) => { - const { element, measurementData } = csToolsAnnotation; - const tool = - csToolsAnnotation.toolType || - csToolsAnnotation.toolName || - measurementData.toolType; - - const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName); - - if (!validToolType(tool)) { - throw new Error('Tool not supported'); - } - - const { - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes(element); - - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID - ); - - const { cachedStats, handles } = measurementData; - - const { start, end } = handles; - - const halfXLength = Math.abs(start.x - end.x) / 2; - const halfYLength = Math.abs(start.y - end.y) / 2; - - const points = []; - const center = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }; - - // To store similar to SR. - if (halfXLength > halfYLength) { - // X-axis major - // Major axis - points.push({ x: center.x - halfXLength, y: center.y }); - points.push({ x: center.x + halfXLength, y: center.y }); - // Minor axis - points.push({ x: center.x, y: center.y - halfYLength }); - points.push({ x: center.x, y: center.y + halfYLength }); - } else { - // Y-axis major - // Major axis - points.push({ x: center.x, y: center.y - halfYLength }); - points.push({ x: center.x, y: center.y + halfYLength }); - // Minor axis - points.push({ x: center.x - halfXLength, y: center.y }); - points.push({ x: center.x + halfXLength, y: center.y }); - } - - let meanSUV; - let stdDevSUV; - - if ( - cachedStats && - cachedStats.meanStdDevSUV && - cachedStats.meanStdDevSUV.mean !== 0 - ) { - const { meanStdDevSUV } = cachedStats; - - meanSUV = meanStdDevSUV.mean; - stdDevSUV = meanStdDevSUV.stdDev; - } - - return { - id: measurementData.id, - SOPInstanceUID, - FrameOfReferenceUID, - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - label: measurementData.label, - description: measurementData.description, - unit: measurementData.unit, - area: cachedStats && cachedStats.area, - mean: cachedStats && cachedStats.mean, - stdDev: cachedStats && cachedStats.stdDev, - meanSUV, - stdDevSUV, - type: getValueTypeFromToolType(tool), - points, - }; - }, -}; - -export default EllipticalRoi; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js index 273e4e44b20..0246c04b05e 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.js @@ -1,10 +1,9 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; -import getHandlesFromPoints from './utils/getHandlesFromPoints'; -import getPointsFromHandles from './utils/getPointsFromHandles'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; +import { DisplaySetService, utils } from '@ohif/core'; const Length = { - toAnnotation: (measurement, definition) => {}, + toAnnotation: measurement => {}, /** * Maps cornerstone annotation event data to measurement service format. @@ -13,49 +12,188 @@ const Length = { * @return {Measurement} Measurement instance */ toMeasurement: ( - csToolsAnnotation, + csToolsEventDetail, DisplaySetService, + CornerstoneViewportService, getValueTypeFromToolType ) => { - const { element, measurementData } = csToolsAnnotation; - const tool = - csToolsAnnotation.toolType || - csToolsAnnotation.toolName || - measurementData.toolType; + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; - const validToolType = toolName => SUPPORTED_TOOLS.includes(toolName); + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); - if (!validToolType(tool)) { + if (!validToolType) { throw new Error('Tool not supported'); } const { SOPInstanceUID, - FrameOfReferenceUID, SeriesInstanceUID, StudyInstanceUID, - } = getSOPInstanceAttributes(element); + } = getSOPInstanceAttributes( + referencedImageId, + CornerstoneViewportService, + viewportId + ); - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID + let displaySet; + + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { points } = data.handles; + + const mappedAnnotations = getMappedAnnotations( + annotation, + DisplaySetService ); + const displayText = getDisplayText(mappedAnnotations, displaySet); + const getReport = () => + _getReport(mappedAnnotations, points, FrameOfReferenceUID); + return { - id: measurementData.id, + uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, + points, + metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, - label: measurementData.label, - description: measurementData.description, - unit: measurementData.unit, - length: measurementData.length, - type: getValueTypeFromToolType(tool), - points: getPointsFromHandles(measurementData.handles), + label: data.label, + displayText: displayText, + data: data.cachedStats, + type: getValueTypeFromToolType(toolName), + getReport, }; }, }; +function getMappedAnnotations(annotation, DisplaySetService) { + const { metadata, data } = annotation; + const { cachedStats } = data; + const { referencedImageId } = metadata; + const targets = Object.keys(cachedStats); + + if (!targets.length) { + return; + } + + const annotations = []; + Object.keys(cachedStats).forEach(targetId => { + const targetStats = cachedStats[targetId]; + + let displaySet; + + if (referencedImageId) { + const { SOPInstanceUID, SeriesInstanceUID } = getSOPInstanceAttributes( + referencedImageId + ); + + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + throw new Error( + 'Non-acquisition plane measurement mapping not supported' + ); + } + + const { SeriesNumber, SeriesInstanceUID } = displaySet; + const { length } = targetStats; + const unit = 'mm'; + + annotations.push({ + SeriesInstanceUID, + SeriesNumber, + unit, + length, + }); + }); + + return annotations; +} + +/* +This function is used to convert the measurement data to a format that is +suitable for the report generation (e.g. for the csv report). The report +returns a list of columns and corresponding values. +*/ +function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { + const columns = []; + const values = []; + + // Add Type + columns.push('AnnotationType'); + values.push('Cornerstone:Length'); + + mappedAnnotations.forEach(annotation => { + const { length } = annotation; + columns.push(`Length (mm)`); + values.push(length); + }); + + if (FrameOfReferenceUID) { + columns.push('FrameOfReferenceUID'); + values.push(FrameOfReferenceUID); + } + + if (points) { + columns.push('points'); + // points has the form of [[x1, y1, z1], [x2, y2, z2], ...] + // convert it to string of [[x1 y1 z1];[x2 y2 z2];...] + // so that it can be used in the csv report + values.push(points.map(p => p.join(' ')).join(';')); + } + + return { + columns, + values, + }; +} + +function getDisplayText(mappedAnnotations, displaySet) { + if (!mappedAnnotations || !mappedAnnotations.length) { + return ''; + } + + const displayText = []; + + // Area is the same for all series + const { length, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0]; + + const instance = displaySet.images.find( + image => image.SOPInstanceUID === SOPInstanceUID + ); + + let InstanceNumber; + if (instance) { + InstanceNumber = instance.InstanceNumber; + } + + const roundedLength = utils.roundNumber(length, 2); + displayText.push( + InstanceNumber + ? `${roundedLength} mm (S: ${SeriesNumber} I: ${InstanceNumber})` + : `${roundedLength} mm (S: ${SeriesNumber})` + ); + + return displayText; +} + export default Length; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js index a1ca5e83e98..ed45a00c954 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/constants/supportedTools.js @@ -1 +1 @@ -export default ['Length', 'EllipticalRoi', 'Bidirectional', 'ArrowAnnotate']; +export default ['Length', 'EllipticalROI', 'Bidirectional', 'ArrowAnnotate']; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js index 069267907fa..2a7776e97f6 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js @@ -1,11 +1,12 @@ import Length from './Length'; import Bidirectional from './Bidirectional'; +import EllipticalROI from './EllipticalROI'; import ArrowAnnotate from './ArrowAnnotate'; -import EllipticalRoi from './EllipticalRoi'; const measurementServiceMappingsFactory = ( MeasurementService, - DisplaySetService + DisplaySetService, + CornerstoneViewportService ) => { /** * Maps measurement service format object to cornerstone annotation object. @@ -19,16 +20,18 @@ const measurementServiceMappingsFactory = ( const { POLYLINE, ELLIPSE, - POINT, + RECTANGLE, BIDIRECTIONAL, + POINT, } = MeasurementService.VALUE_TYPES; - // TODO -> I get why this was attemped, but its not nearly flexible enough. + // TODO -> I get why this was attempted, but its not nearly flexible enough. // A single measurement may have an ellipse + a bidirectional measurement, for instances. // You can't define a bidirectional tool as a single type.. const TOOL_TYPE_TO_VALUE_TYPE = { Length: POLYLINE, - EllipticalRoi: ELLIPSE, + EllipticalROI: ELLIPSE, + RectangleROI: RECTANGLE, Bidirectional: BIDIRECTIONAL, ArrowAnnotate: POINT, }; @@ -43,6 +46,7 @@ const measurementServiceMappingsFactory = ( Length.toMeasurement( csToolsAnnotation, DisplaySetService, + CornerstoneViewportService, _getValueTypeFromToolType ), matchingCriteria: [ @@ -58,6 +62,7 @@ const measurementServiceMappingsFactory = ( Bidirectional.toMeasurement( csToolsAnnotation, DisplaySetService, + CornerstoneViewportService, _getValueTypeFromToolType ), matchingCriteria: [ @@ -73,32 +78,34 @@ const measurementServiceMappingsFactory = ( }, ], }, - ArrowAnnotate: { - toAnnotation: ArrowAnnotate.toAnnotation, + EllipticalROI: { + toAnnotation: EllipticalROI.toAnnotation, toMeasurement: csToolsAnnotation => - ArrowAnnotate.toMeasurement( + EllipticalROI.toMeasurement( csToolsAnnotation, DisplaySetService, + CornerstoneViewportService, _getValueTypeFromToolType ), matchingCriteria: [ { - valueType: MeasurementService.VALUE_TYPES.POINT, - points: 1, + valueType: MeasurementService.VALUE_TYPES.ELLIPSE, }, ], }, - EllipticalRoi: { - toAnnotation: EllipticalRoi.toAnnotation, + ArrowAnnotate: { + toAnnotation: ArrowAnnotate.toAnnotation, toMeasurement: csToolsAnnotation => - EllipticalRoi.toMeasurement( + ArrowAnnotate.toMeasurement( csToolsAnnotation, DisplaySetService, + CornerstoneViewportService, _getValueTypeFromToolType ), matchingCriteria: [ { - valueType: MeasurementService.VALUE_TYPES.ELLIPSE, + valueType: MeasurementService.VALUE_TYPES.POINT, + points: 1, }, ], }, diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.test.js b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.test.js deleted file mode 100644 index 65bd22ae83c..00000000000 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.test.js +++ /dev/null @@ -1,102 +0,0 @@ -import measurementServiceMappingsFactory from './measurementServiceMappingsFactory'; - -jest.mock('cornerstone-core', () => ({ - ...jest.requireActual('cornerstone-core'), - getEnabledElement: () => ({ - image: { imageId: 123 }, - }), - metaData: { - ...jest.requireActual('cornerstone-core').metaData, - get: () => ({ - SOPInstanceUID: '123', - FrameOfReferenceUID: '123', - SeriesInstanceUID: '123', - StudyInstanceUID: '1234', - }), - }, -})); - -describe('measurementServiceMappings.js', () => { - let mappings; - let handles; - let points; - let csToolsAnnotation; - let measurement; - let measurementServiceMock; - let displaySetServiceMock; - let definition = 'Length'; - - beforeEach(() => { - measurementServiceMock = { - VALUE_TYPES: { - POLYLINE: 'value_type::polyline', - POINT: 'value_type::point', - ELLIPSE: 'value_type::ellipse', - MULTIPOINT: 'value_type::multipoint', - CIRCLE: 'value_type::circle', - }, - }; - displaySetServiceMock = { - getDisplaySetForSOPInstanceUID: (SOPInstanceUID, SeriesInstanceUID) => { - console.warn('SOPInstanceUID'); - - return { - displaySetInstanceUID: '1.2.3.4' - } - } - }; - mappings = measurementServiceMappingsFactory(measurementServiceMock, displaySetServiceMock); - handles = { start: { x: 1, y: 2 }, end: { x: 1, y: 2 } }; - points = [ - { x: 1, y: 2 }, - { x: 1, y: 2 }, - ]; - csToolsAnnotation = { - toolName: definition, - measurementData: { - id: 1, - label: 'Test', - SOPInstanceUID: '123', - FrameOfReferenceUID: '123', - SeriesInstanceUID: '123', - handles, - text: 'Test', - description: 'Test', - unit: 'mm', - length: undefined - }, - }; - measurement = { - id: 1, - SOPInstanceUID: '123', - FrameOfReferenceUID: '123', - referenceSeriesUID: '123', - referenceStudyUID: '1234', - displaySetInstanceUID: '1.2.3.4', - label: 'Test', - description: 'Test', - unit: 'mm', - type: measurementServiceMock.VALUE_TYPES.POLYLINE, - points, - }; - jest.clearAllMocks(); - }); - - /*describe('toAnnotation()', () => { - it('map measurement service format to annotation', async () => { - const mappedMeasurement = await mappings[csToolsAnnotation.toolName].toAnnotation( - measurement, - definition - ); - expect(mappedMeasurement).toEqual(csToolsAnnotation); - }); - });*/ - - describe('toMeasurement()', () => { - it('map annotation to measurement service format', async () => { - const getValueTypeFromToolType = (toolType) => 'valueType'; - const mappedAnnotation = await mappings[csToolsAnnotation.toolName].toMeasurement(csToolsAnnotation, displaySetServiceMock, getValueTypeFromToolType); - expect(mappedAnnotation).toEqual(measurement); - }); - }); -}); diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getModalityUnit.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getModalityUnit.js new file mode 100644 index 00000000000..7cc005e4193 --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getModalityUnit.js @@ -0,0 +1,11 @@ +function getModalityUnit(modality) { + if (modality === 'CT') { + return 'HU'; + } else if (modality === 'PT') { + return 'SUV'; + } else { + return ''; + } +} + +export default getModalityUnit; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getPointsFromHandles.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getPointsFromHandles.js deleted file mode 100644 index dfd4d50928d..00000000000 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getPointsFromHandles.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function getPointsFromHandles(handles) { - let points = []; - Object.keys(handles).map(handle => { - if (['start', 'end'].includes(handle)) { - let point = {}; - if (handles[handle].x) point.x = handles[handle].x; - if (handles[handle].y) point.y = handles[handle].y; - points.push(point); - } - }); - return points; -} diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js index 01ca8ffd923..05c66aae270 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js @@ -1,14 +1,69 @@ -import cornerstone from 'cornerstone-core'; +import * as cornerstone from '@cornerstonejs/core'; -export default function getSOPInstanceAttributes(element) { - const enabledElement = cornerstone.getEnabledElement(element); - const imageId = enabledElement.image.imageId; +/** + * It checks if the imageId is provided then it uses it to query + * the metadata and get the SOPInstanceUID, SeriesInstanceUID and StudyInstanceUID. + * If the imageId is not provided then it uses the sceneUID to get the viewports + * inside the scene and then it checks each viewport to find the one that has + * acquisition plane view, and uses the currentImageId of the viewport to + * query the metadata and get UIDs. + * @param {string} imageId The image id of the referenced image + * @param {string} sceneUID The scene UID of the measurement tool + * @returns + */ +export default function getSOPInstanceAttributes( + imageId, + CornerstoneViewportService, + viewportId +) { + if (imageId) { + return _getUIDFromImageID(imageId); + } + + // Todo: implement for volume viewports and use the referencedSeriesInstanceUID + + // if no imageId => measurement is not in the acquisition plane + // const metadata = getUIDFromScene(CornerstoneViewportService, viewportId); + + // if (!metadata) { + // throw new Error('Not viewport with imageId found'); + // } + + // // Since the series and study UID is derived from another viewport in the + // // same scene, we cannot include the SOPInstanceUID + // return { + // SOPInstanceUID: null, + // SeriesInstanceUID: metadata.SeriesInstanceUID, + // StudyInstanceUID: metadata.StudyInstanceUID, + // }; +} + +function _getUIDFromImageID(imageId) { const instance = cornerstone.metaData.get('instance', imageId); return { SOPInstanceUID: instance.SOPInstanceUID, - FrameOfReferenceUID: instance.FrameOfReferenceUID, SeriesInstanceUID: instance.SeriesInstanceUID, StudyInstanceUID: instance.StudyInstanceUID, }; } + +// function getUIDFromScene(CornerstoneViewportService) { +// const renderingEngine = CornerstoneViewportService.getRenderingEngine(); +// const scene = renderingEngine.getScene(sceneUID); + +// const viewportUIDs = scene.getViewportIds(); + +// if (viewportUIDs.length === 0) { +// throw new Error('No viewport found in scene'); +// } + +// for (let i = 0; i < viewportUIDs.length; i++) { +// const vp = renderingEngine.getViewport(viewportUIDs[i]); +// const imageId = vp.getCurrentImageId(); + +// if (imageId) { +// return _getUIDFromImageID(imageId); +// } +// } +// } diff --git a/extensions/cornerstone/src/utils/segmentationServiceMappings/Labelmap.js b/extensions/cornerstone/src/utils/segmentationServiceMappings/Labelmap.js new file mode 100644 index 00000000000..cb058b06e62 --- /dev/null +++ b/extensions/cornerstone/src/utils/segmentationServiceMappings/Labelmap.js @@ -0,0 +1,30 @@ +import { Enums as csToolsEnums } from '@cornerstonejs/tools'; + +const Labelmap = { + toSegmentation: segmentationState => { + const { + activeSegmentIndex, + cachedStats: data, + segmentsLocked, + representationData, + label, + segmentationId, + text, + } = segmentationState; + + const labelmapRepresentationData = + representationData[csToolsEnums.SegmentationRepresentations.Labelmap]; + + return { + id: segmentationId, + activeSegmentIndex, + segmentsLocked, + data, + label, + volumeId: labelmapRepresentationData.volumeId, + displayText: text || [], + }; + }, +}; + +export default Labelmap; diff --git a/extensions/cornerstone/src/utils/segmentationServiceMappings/segmentationServiceMappingsFactory.js b/extensions/cornerstone/src/utils/segmentationServiceMappings/segmentationServiceMappingsFactory.js new file mode 100644 index 00000000000..ea248829abb --- /dev/null +++ b/extensions/cornerstone/src/utils/segmentationServiceMappings/segmentationServiceMappingsFactory.js @@ -0,0 +1,16 @@ +import Labelmap from './Labelmap'; + +const segmentationServiceMappingsFactory = ( + SegmentationService, + DisplaySetService +) => { + return { + Labelmap: { + matchingCriteria: {}, + toSegmentation: csToolsSegmentation => + Labelmap.toSegmentation(csToolsSegmentation, DisplaySetService), + }, + }; +}; + +export default segmentationServiceMappingsFactory; diff --git a/extensions/cornerstone/src/utils/setActiveAndPassiveToolsForElement.js b/extensions/cornerstone/src/utils/setActiveAndPassiveToolsForElement.js deleted file mode 100644 index b8348c99080..00000000000 --- a/extensions/cornerstone/src/utils/setActiveAndPassiveToolsForElement.js +++ /dev/null @@ -1,21 +0,0 @@ -import csTools from 'cornerstone-tools'; - -export default function _setActiveAndPassiveToolsForElement(element, tools) { - const BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool'); - - tools.forEach(tool => { - if (tool.prototype instanceof BaseAnnotationTool) { - // BaseAnnotationTool would likely come from csTools lib exports - const toolName = new tool().name; - csTools.setToolPassiveForElement(element, toolName); // there may be a better place to determine name; may not be on uninstantiated class - } - }); - - csTools.setToolActiveForElement(element, 'Pan', { mouseButtonMask: 4 }); - csTools.setToolActiveForElement(element, 'Zoom', { mouseButtonMask: 2 }); - csTools.setToolActiveForElement(element, 'Wwwc', { mouseButtonMask: 1 }); - csTools.setToolActiveForElement(element, 'StackScrollMouseWheel', {}); // TODO: Empty options should not be required - csTools.setToolActiveForElement(element, 'PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options - csTools.setToolActiveForElement(element, 'ZoomTouchPinch', {}); - csTools.setToolEnabledForElement(element, 'Overlay', {}); -} diff --git a/extensions/default/.webpack/webpack.prod.js b/extensions/default/.webpack/webpack.prod.js index e509d1853ba..33ae8b844a1 100644 --- a/extensions/default/.webpack/webpack.prod.js +++ b/extensions/default/.webpack/webpack.prod.js @@ -13,7 +13,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/extensions/default/package.json b/extensions/default/package.json index ce02c79c460..0c9b59a0c13 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -32,7 +32,7 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/i18n": "^1.0.0", - "dcmjs": "0.16.1", + "dcmjs": "^0.24.5", "dicomweb-client": "^0.6.0", "prop-types": "^15.6.2", "react": "^17.0.2", @@ -42,6 +42,7 @@ "webpack-merge": "^5.7.3" }, "dependencies": { - "@babel/runtime": "7.16.3" + "@babel/runtime": "7.16.3", + "@cornerstonejs/calculate-suv": "1.0.2" } } diff --git a/extensions/default/src/DicomLocalDataSource/index.js b/extensions/default/src/DicomLocalDataSource/index.js index 748bcc08e6d..54f40b400a7 100644 --- a/extensions/default/src/DicomLocalDataSource/index.js +++ b/extensions/default/src/DicomLocalDataSource/index.js @@ -128,6 +128,8 @@ function createDicomLocalApi(dicomLocalConfig) { SOPInstanceUID, } = instance; + instance.imageId = imageId; + // Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI. metadataProvider.addImageIdToUIDs(imageId, { StudyInstanceUID, diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index c1df31e461d..cecd1c8cc35 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -1,4 +1,12 @@ import { api } from 'dicomweb-client'; +import { + DicomMetadataStore, + IWebApiDataSource, + utils, + errorHandler, + classes, +} from '@ohif/core'; + import { mapParams, search as qidoSearch, @@ -7,12 +15,6 @@ import { processSeriesResults, } from './qido.js'; import dcm4cheeReject from './dcm4cheeReject'; -import { - DicomMetadataStore, - IWebApiDataSource, - utils, - errorHandler, -} from '@ohif/core'; import getImageId from './utils/getImageId'; import dcmjs from 'dcmjs'; @@ -31,6 +33,8 @@ const ImplementationClassUID = const ImplementationVersionName = 'OHIF-VIEWER-2.0.0'; const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1'; +const metadataProvider = classes.MetadataProvider; + /** * * @param {string} name - Data source name @@ -94,7 +98,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { query: { studies: { mapParams: mapParams.bind(), - search: async function (origParams) { + search: async function(origParams) { const headers = UserAuthenticationService.getAuthorizationHeader(); if (headers) { qidoDicomWebClient.headers = headers; @@ -119,7 +123,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { }, series: { // mapParams: mapParams.bind(), - search: async function (studyInstanceUid) { + search: async function(studyInstanceUid) { const headers = UserAuthenticationService.getAuthorizationHeader(); if (headers) { qidoDicomWebClient.headers = headers; @@ -163,13 +167,13 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { * @returns an absolute URL to the resource, if the absolute URL can be retrieved as singlepart, * or is already retrieved, or a promise to a URL for such use if a BulkDataURI */ - directURL: (params) => { + directURL: params => { const { instance, - tag = "PixelData", - defaultPath = "/pixeldata", - defaultType = "video/mp4", - singlepart: fetchPart = "video", + tag = 'PixelData', + defaultPath = '/pixeldata', + defaultType = 'video/mp4', + singlepart: fetchPart = 'video', } = params; const value = instance[tag]; if (!value) return undefined; @@ -180,10 +184,15 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { value.DirectRetrieveURL = URL.createObjectURL(blob); return value.DirectRetrieveURL; } - if (!singlepart || singlepart !== true && singlepart.indexOf(fetchPart) === -1) { + if ( + !singlepart || + (singlepart !== true && singlepart.indexOf(fetchPart) === -1) + ) { if (value.retrieveBulkData) { return value.retrieveBulkData().then(arr => { - value.DirectRetrieveURL = URL.createObjectURL(new Blob([arr], { type: defaultType })); + value.DirectRetrieveURL = URL.createObjectURL( + new Blob([arr], { type: defaultType }) + ); return value.DirectRetrieveURL; }); } @@ -191,7 +200,11 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { return undefined; } - const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance; + const { + StudyInstanceUID, + SeriesInstanceUID, + SOPInstanceUID, + } = instance; const BulkDataURI = (value && value.BulkDataURI) || `series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`; @@ -234,94 +247,27 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { ); } - // Get Series - const { - seriesSummaryMetadata, - seriesPromises, - } = await retrieveStudyMetadata( - wadoDicomWebClient, - StudyInstanceUID, - enableStudyLazyLoad, - filters, - sortCriteria, - sortFunction - ); - - /** - * naturalizes the dataset, and adds a retrieve bulkdata method - * to any values containing BulkDataURI. - * @param {*} instance - * @returns naturalized dataset, with retrieveBulkData methods - */ - const addRetrieveBulkData = instance => { - const naturalized = naturalizeDataset(instance); - Object.keys(naturalized).forEach(key => { - const value = naturalized[key]; - // The value.Value will be set with the bulkdata read value - // in which case it isn't necessary to re-read this. - if (value && value.BulkDataURI && !value.Value) { - // Provide a method to fetch bulkdata - value.retrieveBulkData = () => { - const options = { - // The bulkdata fetches work with either multipart or - // singlepart, so set multipart to false to let the server - // decide which type to respond with. - multipart: false, - BulkDataURI: value.BulkDataURI, - // The study instance UID is required if the bulkdata uri - // is relative - that isn't disallowed by DICOMweb, but - // isn't well specified in the standard, but is needed in - // any implementation that stores static copies of the metadata - StudyInstanceUID: naturalized.StudyInstanceUID, - }; - return qidoDicomWebClient - .retrieveBulkData(options) - .then(val => { - const ret = (val && val[0]) || undefined; - value.Value = ret; - return ret; - }); - }; - } - }); - return naturalized; - }; - - // Async load series, store as retrieved - function storeInstances(instances) { - const naturalizedInstances = instances.map(addRetrieveBulkData); - - DicomMetadataStore.addInstances(naturalizedInstances, madeInClient); - } - - function setSuccessFlag() { - const study = DicomMetadataStore.getStudy( + if (enableStudyLazyLoad) { + return implementation._retrieveSeriesMetadataAsync( StudyInstanceUID, + filters, + sortCriteria, + sortFunction, madeInClient ); - study.isLoaded = true; } - // Google Cloud Healthcare doesn't return StudyInstanceUID, so we need to add - // it manually here - seriesSummaryMetadata.forEach(aSeries => { - aSeries.StudyInstanceUID = StudyInstanceUID; - }); - - DicomMetadataStore.addSeriesMetadata( - seriesSummaryMetadata, + return implementation._retrieveSeriesMetadataSync( + StudyInstanceUID, + filters, + sortCriteria, + sortFunction, madeInClient ); - - const numberOfSeries = seriesPromises.length; - seriesPromises.forEach(async (seriesPromise, index) => { - const instances = await seriesPromise; - storeInstances(instances); - if (index === numberOfSeries - 1) setSuccessFlag(); - }); }, }, }, + store: { dicom: async dataset => { const headers = UserAuthenticationService.getAuthorizationHeader(); @@ -353,6 +299,187 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { await wadoDicomWebClient.storeInstances(options); }, }, + _retrieveSeriesMetadataSync: async ( + StudyInstanceUID, + filters, + sortCriteria, + sortFunction, + madeInClient + ) => { + const enableStudyLazyLoad = false; + + // data is all SOPInstanceUIDs + const data = await retrieveStudyMetadata( + wadoDicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction + ); + + // first naturalize the data + const naturalizedInstancesMetadata = data.map(naturalizeDataset); + + const seriesSummaryMetadata = {}; + const instancesPerSeries = {}; + + naturalizedInstancesMetadata.forEach(instance => { + if (!seriesSummaryMetadata[instance.SeriesInstanceUID]) { + seriesSummaryMetadata[instance.SeriesInstanceUID] = { + StudyInstanceUID: instance.StudyInstanceUID, + StudyDescription: instance.StudyDescription, + SeriesInstanceUID: instance.SeriesInstanceUID, + SeriesDescription: instance.SeriesDescription, + SeriesNumber: instance.SeriesNumber, + SeriesTime: instance.SeriesTime, + SOPClassUID: instance.SOPClassUID, + ProtocolName: instance.ProtocolName, + Modality: instance.Modality, + }; + } + + if (!instancesPerSeries[instance.SeriesInstanceUID]) { + instancesPerSeries[instance.SeriesInstanceUID] = []; + } + + const imageId = implementation.getImageIdsForInstance({ + instance, + }); + + instance.imageId = imageId; + + metadataProvider.addImageIdToUIDs(imageId, { + StudyInstanceUID, + SeriesInstanceUID: instance.SeriesInstanceUID, + SOPInstanceUID: instance.SOPInstanceUID, + }); + + instancesPerSeries[instance.SeriesInstanceUID].push(instance); + }); + + // grab all the series metadata + const seriesMetadata = Object.values(seriesSummaryMetadata); + DicomMetadataStore.addSeriesMetadata(seriesMetadata, madeInClient); + + Object.keys(instancesPerSeries).forEach(seriesInstanceUID => + DicomMetadataStore.addInstances( + instancesPerSeries[seriesInstanceUID], + madeInClient + ) + ); + }, + + _retrieveSeriesMetadataAsync: async ( + StudyInstanceUID, + filters, + sortCriteria, + sortFunction, + madeInClient = false + ) => { + const enableStudyLazyLoad = true; + // Get Series + const { + preLoadData: seriesSummaryMetadata, + promises: seriesPromises, + } = await retrieveStudyMetadata( + wadoDicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction + ); + + /** + * naturalizes the dataset, and adds a retrieve bulkdata method + * to any values containing BulkDataURI. + * @param {*} instance + * @returns naturalized dataset, with retrieveBulkData methods + */ + const addRetrieveBulkData = instance => { + const naturalized = naturalizeDataset(instance); + Object.keys(naturalized).forEach(key => { + const value = naturalized[key]; + // The value.Value will be set with the bulkdata read value + // in which case it isn't necessary to re-read this. + if (value && value.BulkDataURI && !value.Value) { + // Provide a method to fetch bulkdata + value.retrieveBulkData = () => { + const options = { + // The bulkdata fetches work with either multipart or + // singlepart, so set multipart to false to let the server + // decide which type to respond with. + multipart: false, + BulkDataURI: value.BulkDataURI, + // The study instance UID is required if the bulkdata uri + // is relative - that isn't disallowed by DICOMweb, but + // isn't well specified in the standard, but is needed in + // any implementation that stores static copies of the metadata + StudyInstanceUID: naturalized.StudyInstanceUID, + }; + return qidoDicomWebClient.retrieveBulkData(options).then(val => { + const ret = (val && val[0]) || undefined; + value.Value = ret; + return ret; + }); + }; + } + }); + return naturalized; + }; + + // Async load series, store as retrieved + function storeInstances(instances) { + const naturalizedInstances = instances.map(addRetrieveBulkData); + + // Adding instanceMetadata to OHIF MetadataProvider + naturalizedInstances.forEach((instance, index) => { + const imageId = implementation.getImageIdsForInstance({ + instance, + }); + + // Adding imageId to each instance + // Todo: This is not the best way I can think of to let external + // metadata handlers know about the imageId that is stored in the store + instance.imageId = imageId; + + // Adding UIDs to metadataProvider + // Note: storing imageURI in metadataProvider since stack viewports + // will use the same imageURI + metadataProvider.addImageIdToUIDs(imageId, { + StudyInstanceUID, + SeriesInstanceUID: instance.SeriesInstanceUID, + SOPInstanceUID: instance.SOPInstanceUID, + }); + }); + + DicomMetadataStore.addInstances(naturalizedInstances, madeInClient); + } + + function setSuccessFlag() { + const study = DicomMetadataStore.getStudy( + StudyInstanceUID, + madeInClient + ); + study.isLoaded = true; + } + + // Google Cloud Healthcare doesn't return StudyInstanceUID, so we need to add + // it manually here + seriesSummaryMetadata.forEach(aSeries => { + aSeries.StudyInstanceUID = StudyInstanceUID; + }); + + DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient); + + const numberOfSeries = seriesPromises.length; + seriesPromises.forEach(async (seriesPromise, index) => { + const instances = await seriesPromise; + storeInstances(instances); + if (index === numberOfSeries - 1) setSuccessFlag(); + }); + }, deleteStudyMetadataPromise, getImageIdsForDisplaySet(displaySet) { const images = displaySet.images; diff --git a/extensions/default/src/DicomWebDataSource/utils/sortStudy.js b/extensions/default/src/DicomWebDataSource/utils/sortStudy.js index cac0334aa78..467d6fcc734 100644 --- a/extensions/default/src/DicomWebDataSource/utils/sortStudy.js +++ b/extensions/default/src/DicomWebDataSource/utils/sortStudy.js @@ -25,7 +25,7 @@ const seriesSortCriteria = { }; const instancesSortCriteria = { - default: (a, b) => a.InstanceNumber - b.InstanceNumber, + default: (a, b) => parseInt(a.InstanceNumber) - parseInt(b.InstanceNumber), }; const sortingCriteria = { diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js index c6fcf6b9295..d70a2cdccd0 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js @@ -1,4 +1,4 @@ -//import RetrieveMetadataLoaderSync from './retrieveMetadataLoaderSync'; +import RetrieveMetadataLoaderSync from './retrieveMetadataLoaderSync'; import RetrieveMetadataLoaderAsync from './retrieveMetadataLoaderAsync'; /** @@ -19,12 +19,10 @@ async function RetrieveMetadata( sortCriteria, sortFunction ) { - // const RetrieveMetadataLoader = - // enableStudyLazyLoad !== false - // ? RetrieveMetadataLoaderAsync - // : RetrieveMetadataLoaderSync; - - const RetrieveMetadataLoader = RetrieveMetadataLoaderAsync; + const RetrieveMetadataLoader = + enableStudyLazyLoad !== false + ? RetrieveMetadataLoaderAsync + : RetrieveMetadataLoaderSync; const retrieveMetadataLoader = new RetrieveMetadataLoader( dicomWebClient, @@ -33,12 +31,9 @@ async function RetrieveMetadata( sortCriteria, sortFunction ); - const { preLoadData, promises } = await retrieveMetadataLoader.execLoad(); + const data = await retrieveMetadataLoader.execLoad(); - return { - seriesSummaryMetadata: preLoadData, - seriesPromises: promises, - }; + return data; } export default RetrieveMetadata; diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js index 86c19567743..3d7294edf4d 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js @@ -2,7 +2,6 @@ import dcmjs from 'dcmjs'; import { sortStudySeries, sortingCriteria } from '../utils/sortStudy'; import RetrieveMetadataLoader from './retrieveMetadataLoader'; - /** * Creates an immutable series loader object which loads each series sequentially using the iterator interface * @param {DICOMWebClient} dicomWebClient The DICOMWebClient instance to be used for series load diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js index 0a53817e547..6ca47ce9db2 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js @@ -60,7 +60,6 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader { } async posLoad(loadData) { - const { server } = this; - return createStudyFromSOPInstanceList(server, loadData); + return loadData; } } diff --git a/extensions/default/src/PanelMeasurementTable.js b/extensions/default/src/PanelMeasurementTable.js deleted file mode 100644 index 62f594298d2..00000000000 --- a/extensions/default/src/PanelMeasurementTable.js +++ /dev/null @@ -1,168 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import { MeasurementTable } from '@ohif/ui'; -import { DicomMetadataStore } from '@ohif/core'; -import debounce from './debounce.js'; - -export default function PanelMeasurementTable({ - servicesManager, - // commandsManager, -}) { - const { MeasurementService } = servicesManager.services; - const [displayMeasurements, setDisplayMeasurements] = useState([]); - - useEffect(() => { - const debouncedSetDisplayMeasurements = debounce( - setDisplayMeasurements, - 100 - ); - // ~~ Initial - setDisplayMeasurements(_getMappedMeasurements(MeasurementService)); - - // ~~ Subscription - const added = MeasurementService.EVENTS.MEASUREMENT_ADDED; - const addedRaw = MeasurementService.EVENTS.RAW_MEASUREMENT_ADDED; - const updated = MeasurementService.EVENTS.MEASUREMENT_UPDATED; - const removed = MeasurementService.EVENTS.MEASUREMENT_REMOVED; - const cleared = MeasurementService.EVENTS.MEASUREMENTS_CLEARED; - const subscriptions = []; - - [added, addedRaw, updated, removed, cleared].forEach(evt => { - subscriptions.push( - MeasurementService.subscribe(evt, () => { - debouncedSetDisplayMeasurements( - _getMappedMeasurements(MeasurementService) - ); - }).unsubscribe - ); - }); - - return () => { - subscriptions.forEach(unsub => { - unsub(); - }); - }; - }, [MeasurementService]); - - return ( -
- alert(`Click: ${id}`)} - onEdit={({ id }) => alert(`Edit: ${id}`)} - /> -
- ); -} - -PanelMeasurementTable.propTypes = { - servicesManager: PropTypes.shape({ - services: PropTypes.shape({ - MeasurementService: PropTypes.shape({ - getMeasurements: PropTypes.func.isRequired, - subscribe: PropTypes.func.isRequired, - EVENTS: PropTypes.object.isRequired, - VALUE_TYPES: PropTypes.object.isRequired, - }).isRequired, - }).isRequired, - }).isRequired, -}; - -function _getMappedMeasurements(MeasurementService) { - const measurements = MeasurementService.getMeasurements(); - const mappedMeasurements = measurements.map((m, index) => - _mapMeasurementToDisplay(m, index, MeasurementService.VALUE_TYPES) - ); - - return mappedMeasurements; -} - -function _mapMeasurementToDisplay(measurement, index, types) { - const { - id, - label, - description, - // Reference IDs - referenceStudyUID, - referenceSeriesUID, - SOPInstanceUID, - } = measurement; - const instance = DicomMetadataStore.getInstance( - referenceStudyUID, - referenceSeriesUID, - SOPInstanceUID - ); - const { PixelSpacing, SeriesNumber, InstanceNumber } = instance; - - return { - id: index + 1, - label: '(empty)', // 'Label short description', - displayText: - _getDisplayText( - measurement, - PixelSpacing, - SeriesNumber, - InstanceNumber, - types - ) || [], - // TODO: handle one layer down - isActive: false, // activeMeasurementItem === i + 1, - }; -} - -function _getDisplayText( - measurement, - pixelSpacing, - seriesNumber, - instanceNumber, - types -) { - const { type, points } = measurement; - const hasPixelSpacing = - pixelSpacing !== undefined && - Array.isArray(pixelSpacing) && - pixelSpacing.length === 2; - const [rowPixelSpacing, colPixelSpacing] = hasPixelSpacing - ? pixelSpacing - : [1, 1]; - const unit = hasPixelSpacing ? 'mm' : 'px'; - - switch (type) { - case types.POLYLINE: { - const { length } = measurement; - const roundedLength = _round(length, 1); - - return [ - `${roundedLength} ${unit} (S:${seriesNumber}, I:${instanceNumber})`, - ]; - } - case types.BIDIRECTIONAL: { - const { shortestDiameter, longestDiameter } = measurement; - const roundedShortestDiameter = _round(shortestDiameter, 1); - const roundedLongestDiameter = _round(longestDiameter, 1); - - return [ - `l: ${roundedLongestDiameter} ${unit} (S:${seriesNumber}, I:${instanceNumber})`, - `s: ${roundedShortestDiameter} ${unit}`, - ]; - } - case types.ELLIPSE: { - const { area } = measurement; - const roundedArea = _round(area, 1); - - return [ - `${roundedArea} ${unit}2 (S:${seriesNumber}, I:${instanceNumber})`, - ]; - } - case types.POINT: { - const { text } = measurement; - return [`${text} (S:${seriesNumber}, I:${instanceNumber})`]; - } - } -} - -function _round(value, decimals) { - return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); -} diff --git a/extensions/default/src/Panels/ActionButtons.tsx b/extensions/default/src/Panels/ActionButtons.tsx new file mode 100644 index 00000000000..292bae6843b --- /dev/null +++ b/extensions/default/src/Panels/ActionButtons.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; + +import { Button, ButtonGroup } from '@ohif/ui'; + +function ActionButtons({ onExportClick, onCreateReportClick }) { + const { t } = useTranslation('MeasurementTable'); + + return ( + + + + + {/* */} + + ); +} + +ActionButtons.propTypes = { + onExportClick: PropTypes.func, + onCreateReportClick: PropTypes.func, +}; + +ActionButtons.defaultProps = { + onExportClick: () => alert('Export'), + onCreateReportClick: () => alert('Create Report'), +}; + +export default ActionButtons; diff --git a/extensions/default/src/Panels/PanelMeasurementTable.tsx b/extensions/default/src/Panels/PanelMeasurementTable.tsx new file mode 100644 index 00000000000..c076692ca80 --- /dev/null +++ b/extensions/default/src/Panels/PanelMeasurementTable.tsx @@ -0,0 +1,212 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { MeasurementTable, Dialog, Input, useViewportGrid } from '@ohif/ui'; +import ActionButtons from './ActionButtons'; +import debounce from 'lodash.debounce'; + +import { utils } from '@ohif/core'; + +const { downloadCSVReport } = utils; + +// tools with measurements to display inside the panel +const MEASUREMENT_TOOLS = [ + 'EllipticalROI', + 'RectangleROI', + 'Length', + 'Bidirectional', +]; + +export default function PanelMeasurementTable({ + servicesManager, + commandsManager, +}) { + const [viewportGrid, viewportGridService] = useViewportGrid(); + const { MeasurementService, UIDialogService } = servicesManager.services; + const [displayMeasurements, setDisplayMeasurements] = useState([]); + + useEffect(() => { + const debouncedSetDisplayMeasurements = debounce( + setDisplayMeasurements, + 100 + ); + // ~~ Initial + setDisplayMeasurements(_getMappedMeasurements(MeasurementService)); + + // ~~ Subscription + const added = MeasurementService.EVENTS.MEASUREMENT_ADDED; + const addedRaw = MeasurementService.EVENTS.RAW_MEASUREMENT_ADDED; + const updated = MeasurementService.EVENTS.MEASUREMENT_UPDATED; + const removed = MeasurementService.EVENTS.MEASUREMENT_REMOVED; + const cleared = MeasurementService.EVENTS.MEASUREMENTS_CLEARED; + const subscriptions = []; + + [added, addedRaw, updated, removed, cleared].forEach(evt => { + subscriptions.push( + MeasurementService.subscribe(evt, () => { + debouncedSetDisplayMeasurements( + _getMappedMeasurements(MeasurementService) + ); + }).unsubscribe + ); + }); + + return () => { + subscriptions.forEach(unsub => { + unsub(); + }); + debouncedSetDisplayMeasurements.cancel(); + }; + }, []); + + async function exportReport() { + const measurements = MeasurementService.getMeasurements(); + + downloadCSVReport(measurements, MeasurementService); + } + + const jumpToImage = ({ uid, isActive }) => { + MeasurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid); + + onMeasurementItemClickHandler({ uid, isActive }); + }; + + const onMeasurementItemEditHandler = ({ uid, isActive }) => { + const measurement = MeasurementService.getMeasurement(uid); + //Todo: why we are jumping to image? + // jumpToImage({ id, isActive }); + + const onSubmitHandler = ({ action, value }) => { + switch (action.id) { + case 'save': { + MeasurementService.update( + uid, + { + ...measurement, + ...value, + }, + true + ); + } + } + UIDialogService.dismiss({ id: 'enter-annotation' }); + }; + + UIDialogService.create({ + id: 'enter-annotation', + centralize: true, + isDraggable: false, + showOverlay: true, + content: Dialog, + contentProps: { + title: 'Enter your annotation', + noCloseButton: true, + value: { label: measurement.label || '' }, + body: ({ value, setValue }) => { + const onChangeHandler = event => { + event.persist(); + setValue(value => ({ ...value, label: event.target.value })); + }; + + const onKeyPressHandler = event => { + if (event.key === 'Enter') { + onSubmitHandler({ value, action: { id: 'save' } }); + } + }; + return ( +
+ +
+ ); + }, + actions: [ + // temp: swap button types until colors are updated + { id: 'cancel', text: 'Cancel', type: 'primary' }, + { id: 'save', text: 'Save', type: 'secondary' }, + ], + onSubmit: onSubmitHandler, + }, + }); + }; + + const onMeasurementItemClickHandler = ({ uid, isActive }) => { + if (!isActive) { + const measurements = [...displayMeasurements]; + const measurement = measurements.find(m => m.uid === uid); + + measurements.forEach(m => (m.isActive = m.uid !== uid ? false : true)); + measurement.isActive = true; + setDisplayMeasurements(measurements); + } + }; + + return ( + <> +
+ +
+
+ {}} + /> +
+ + ); +} + +PanelMeasurementTable.propTypes = { + servicesManager: PropTypes.shape({ + services: PropTypes.shape({ + MeasurementService: PropTypes.shape({ + getMeasurements: PropTypes.func.isRequired, + subscribe: PropTypes.func.isRequired, + EVENTS: PropTypes.object.isRequired, + VALUE_TYPES: PropTypes.object.isRequired, + }).isRequired, + }).isRequired, + }).isRequired, +}; + +function _getMappedMeasurements(MeasurementService) { + const measurements = MeasurementService.getMeasurements(); + // filter out measurements whose toolName is not in MEASUREMENT_TOOLS + const measurementTools = measurements.filter(measurement => + MEASUREMENT_TOOLS.includes(measurement.toolName) + ); + + const mappedMeasurements = measurementTools.map((m, index) => + _mapMeasurementToDisplay(m, index, MeasurementService.VALUE_TYPES) + ); + + return mappedMeasurements; +} + +function _mapMeasurementToDisplay(measurement, index, types) { + const { displayText, uid, label, type } = measurement; + + return { + uid, + label: label || '(empty)', + measurementType: type, + displayText: displayText || [], + // TODO: handle one layer down + isActive: false, // activeMeasurementItem === i + 1, + }; +} diff --git a/extensions/default/src/Panels/PanelStudyBrowser.jsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx similarity index 93% rename from extensions/default/src/Panels/PanelStudyBrowser.jsx rename to extensions/default/src/Panels/PanelStudyBrowser.tsx index d375237c8e1..72fed50eae9 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.jsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; -import { StudyBrowser, useImageViewer } from '@ohif/ui'; +import { StudyBrowser, useImageViewer, useViewportGrid } from '@ohif/ui'; import { utils } from '@ohif/core'; const { formatDate } = utils; @@ -20,6 +20,10 @@ function PanelStudyBrowser({ // doesn't have to have such an intense shape. This works well enough for now. // Tabs --> Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); + const [ + { activeViewportIndex, viewports }, + viewportGridService, + ] = useViewportGrid(); const [activeTabName, setActiveTabName] = useState('primary'); const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([ ...StudyInstanceUIDs, @@ -29,6 +33,13 @@ function PanelStudyBrowser({ const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({}); const isMounted = useRef(true); + const onDoubleClickThumbnailHandler = displaySetInstanceUID => { + viewportGridService.setDisplaySetsForViewport({ + viewportIndex: activeViewportIndex, + displaySetInstanceUIDs: [displaySetInstanceUID], + }); + }; + // ~~ studyDisplayList useEffect(() => { // Fetch all studies for the patient in each primary study @@ -38,6 +49,7 @@ function PanelStudyBrowser({ // TODO: This should be "naturalized DICOM JSON" studies const mappedStudies = _mapDataSourceStudies(qidoStudiesForPatient); + const actuallyMappedStudies = mappedStudies.map(qidoStudy => { return { studyInstanceUid: qidoStudy.StudyInstanceUID, @@ -57,7 +69,7 @@ function PanelStudyBrowser({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [StudyInstanceUIDs, getStudiesForPatientByStudyInstanceUID]); - // ~~ Initial Thumbnails + // // ~~ Initial Thumbnails useEffect(() => { const currentDisplaySets = DisplaySetService.activeDisplaySets; currentDisplaySets.forEach(async dSet => { @@ -68,7 +80,7 @@ function PanelStudyBrowser({ const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); const imageId = imageIds[Math.floor(imageIds.length / 2)]; - // TODO: Is it okay that imageIds are not returned here for SR displaysets? + // TODO: Is it okay that imageIds are not returned here for SR displaySets? if (imageId) { // When the image arrives, render it and store the result in the thumbnailImgSrcMap newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( @@ -184,10 +196,15 @@ function PanelStudyBrowser({ } } + const activeDisplaySetInstanceUIDs = + viewports[activeViewportIndex]?.displaySetInstanceUIDs; + return ( { diff --git a/extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx similarity index 89% rename from extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx rename to extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx index e79deea68ba..7ae799a865e 100644 --- a/extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx +++ b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx @@ -26,7 +26,7 @@ function WrappedPanelStudyBrowser({ dataSource ); const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn( - commandsManager.getCommand.bind(commandsManager) + extensionManager ); const _requestDisplaySetCreationForStudy = requestDisplaySetCreationForStudy.bind( null, @@ -55,14 +55,13 @@ function WrappedPanelStudyBrowser({ * @returns {func} getImageSrcFromImageId - A utility function powered by * cornerstone */ -function _createGetImageSrcFromImageIdFn(getCommand) { - try { - const command = getCommand('getCornerstoneLibraries', 'VIEWER'); - if (!command) { - return; - } - const { cornerstone } = command.commandFn(); +function _createGetImageSrcFromImageIdFn(extensionManager) { + const utilities = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' + ); + try { + const { cornerstone } = utilities.exports.getCornerstoneLibraries(); return getImageSrcFromImageId.bind(null, cornerstone); } catch (ex) { throw new Error('Required command not found'); diff --git a/extensions/default/src/debounce.js b/extensions/default/src/Panels/debounce.js similarity index 100% rename from extensions/default/src/debounce.js rename to extensions/default/src/Panels/debounce.js diff --git a/extensions/default/src/Panels/getImageSrcFromImageId.js b/extensions/default/src/Panels/getImageSrcFromImageId.js index baf46c84a31..f243e4de42d 100644 --- a/extensions/default/src/Panels/getImageSrcFromImageId.js +++ b/extensions/default/src/Panels/getImageSrcFromImageId.js @@ -4,16 +4,13 @@ */ function getImageSrcFromImageId(cornerstone, imageId) { return new Promise((resolve, reject) => { - cornerstone - .loadAndCacheImage(imageId) - .then(image => { - const canvas = document.createElement('canvas'); - cornerstone.renderToCanvas(canvas, image); - + const canvas = document.createElement('canvas'); + cornerstone.utilities + .loadImageToCanvas(canvas, imageId) + .then(imageId => { resolve(canvas.toDataURL()); }) .catch(reject); }); } - export default getImageSrcFromImageId; diff --git a/extensions/default/src/Panels/getStudiesForPatientByStudyInstanceUID.js b/extensions/default/src/Panels/getStudiesForPatientByStudyInstanceUID.js index ae0ed4a9e33..fb5baa5395d 100644 --- a/extensions/default/src/Panels/getStudiesForPatientByStudyInstanceUID.js +++ b/extensions/default/src/Panels/getStudiesForPatientByStudyInstanceUID.js @@ -21,6 +21,9 @@ async function getStudiesForPatientByStudyInstanceUID( patientId: getStudyResult[0].mrn, }); } + console.log('No mrn found for', getStudyResult); + // The original study we KNOW belongs to the same set, so just return it + return getStudyResult; } export default getStudiesForPatientByStudyInstanceUID; diff --git a/extensions/default/src/Panels/index.js b/extensions/default/src/Panels/index.js index c596e03a07d..a5a81473ca7 100644 --- a/extensions/default/src/Panels/index.js +++ b/extensions/default/src/Panels/index.js @@ -1,4 +1,5 @@ import PanelStudyBrowser from './PanelStudyBrowser'; import WrappedPanelStudyBrowser from './WrappedPanelStudyBrowser'; +import PanelMeasurementTable from './PanelMeasurementTable'; -export { PanelStudyBrowser, WrappedPanelStudyBrowser }; +export { PanelStudyBrowser, WrappedPanelStudyBrowser, PanelMeasurementTable }; diff --git a/extensions/default/src/Toolbar/ToolbarDivider.jsx b/extensions/default/src/Toolbar/ToolbarDivider.tsx similarity index 100% rename from extensions/default/src/Toolbar/ToolbarDivider.jsx rename to extensions/default/src/Toolbar/ToolbarDivider.tsx diff --git a/extensions/default/src/Toolbar/ToolbarLayoutSelector.jsx b/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx similarity index 100% rename from extensions/default/src/Toolbar/ToolbarLayoutSelector.jsx rename to extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx diff --git a/extensions/default/src/Toolbar/ToolbarSplitButton.jsx b/extensions/default/src/Toolbar/ToolbarSplitButton.tsx similarity index 100% rename from extensions/default/src/Toolbar/ToolbarSplitButton.jsx rename to extensions/default/src/Toolbar/ToolbarSplitButton.tsx diff --git a/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx b/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.tsx similarity index 100% rename from extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx rename to extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.tsx diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.tsx similarity index 96% rename from extensions/default/src/ViewerLayout/index.jsx rename to extensions/default/src/ViewerLayout/index.tsx index d377f688de2..e9f240f0187 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -201,11 +201,11 @@ function ViewerLayout({
{/* LEFT SIDEPANELS */} - {leftPanelComponents.length && ( + {leftPanelComponents.length ? ( - )} + ) : null} {/* TOOLBAR + GRID */}
-
+
- {rightPanelComponents.length && ( + {rightPanelComponents.length ? ( - )} + ) : null}
); diff --git a/extensions/default/src/commandsModule.js b/extensions/default/src/commandsModule.js index 9556cc89689..d5d9aca35e2 100644 --- a/extensions/default/src/commandsModule.js +++ b/extensions/default/src/commandsModule.js @@ -1,25 +1,21 @@ const commandsModule = ({ servicesManager, commandsManager }) => { const { MeasurementService, - ViewportGridService, - ToolBarService, HangingProtocolService, - CineService, + UINotificationService, } = servicesManager.services; const actions = { + displayNotification: ({ text, title, type }) => { + UINotificationService.show({ + title: title, + message: text, + type: type, + }); + }, clearMeasurements: () => { MeasurementService.clear(); }, - toggleCine: () => { - const { viewports } = ViewportGridService.getState(); - const { isCineEnabled } = CineService.getState(); - CineService.setIsCineEnabled(!isCineEnabled); - ToolBarService.setButton('Cine', { props: { isActive: !isCineEnabled } }); - viewports.forEach((_, index) => - CineService.setCine({ id: index, isPlaying: false }) - ); - }, nextStage: () => { // next stage in hanging protocols HangingProtocolService.nextProtocolStage(); @@ -35,8 +31,8 @@ const commandsModule = ({ servicesManager, commandsManager }) => { storeContexts: [], options: {}, }, - toggleCine: { - commandFn: actions.toggleCine, + displayNotification: { + commandFn: actions.displayNotification, storeContexts: [], options: {}, }, diff --git a/extensions/default/src/getHangingProtocolModule.js b/extensions/default/src/getHangingProtocolModule.js index 0d4e9b2975c..c85fac71a4d 100644 --- a/extensions/default/src/getHangingProtocolModule.js +++ b/extensions/default/src/getHangingProtocolModule.js @@ -1,186 +1,61 @@ -const hangingProtocolName = 'petCT'; - -const petCTProtocol = { - id: 'PET/CT', +const defaultProtocol = { + id: 'default', locked: true, hasUpdatedPriorsInformation: false, - name: 'PET/CT', - createdDate: '2021-02-23T18:32:42.849Z', - modifiedDate: '2021-02-23T18:32:42.849Z', + name: 'Default', + createdDate: '2021-02-23T19:22:08.894Z', + modifiedDate: '2021-02-23T19:22:08.894Z', availableTo: {}, editableBy: {}, - protocolMatchingRules: [ - { - id: 'wauZK2QNEfDPwcAQo', - weight: 1, - attribute: 'StudyInstanceUID', - constraint: { - equals: { - value: '1.3.6.1.4.1.25403.345050719074.3824.20170125112931.11', - }, - }, - required: true, - }, - ], + protocolMatchingRules: [], + toolGroupIds: ['default'], stages: [ { id: 'hYbmMy3b7pz7GLiaT', - name: 'oneByTwo', + name: 'default', viewportStructure: { type: 'grid', properties: { - rows: 2, - columns: 2, + rows: 1, + columns: 1, }, }, - viewports: [ + displaySets: [ { - viewportSettings: [ - { - options: { - voi: { - windowWidth: 500, - windowCenter: 500, - }, - }, - commandName: '', - // Type can be viewport or prop - // viewport: It is most suited for settings that - // should be applied before the first render - // prop: It is the type of command that can be applied - // after the image render, such as tool activations. - type: 'viewport', - }, - ], + id: 'displaySet', imageMatchingRules: [], - seriesMatchingRules: [ - { - id: 'vSjk7NCYjtdS3XZAw', - weight: 1, - attribute: 'SeriesNumber', - constraint: { - equals: { - value: "4", - }, - }, - required: false, - }, - ], + seriesMatchingRules: [], studyMatchingRules: [], }, + ], + viewports: [ { - viewportSettings: [ - { - options: { - invert: true, - }, - commandName: '', - type: 'viewport', - }, - ], - imageMatchingRules: [ - - ], - seriesMatchingRules: [ - { - id: 'GPEYqFLv2dwzCM322', - weight: 1, - attribute: 'SeriesNumber', - constraint: { - equals: { - value: "1", - }, - }, - required: false, - }, - ], - studyMatchingRules: [ - - ], - }, - { - viewportSettings: [ - { - options: { - invert: true, - }, - commandName: '', - type: 'viewport', - }, - ], - imageMatchingRules: [ - - ], - seriesMatchingRules: [ - { - id: 'GPEYqFLv2dwzCM322', - weight: 1, - attribute: 'SeriesNumber', - constraint: { - equals: { - value: "6", - }, - }, - required: false, - }, - ], - studyMatchingRules: [ - - ], - }, - { - viewportSettings: [ - { - options: { - invert: true, - }, - commandName: '', - type: 'viewport', - }, - ], - imageMatchingRules: [ - - ], - seriesMatchingRules: [ - { - id: 'GPEYqFLv2dwzCM322', - weight: 1, - attribute: 'SeriesDescription', - constraint: { - contains: { - value: "Corrected", - }, - }, - required: false, - }, + viewportOptions: { + toolGroupId: 'default', + // initialImageOptions: { + // index: 180, + // preset: 'middle', // 'first', 'last', 'middle' + // }, + }, + displaySets: [ { - id: 'GPEYqFLv2dwzCM322', - weight: 2, - attribute: 'SeriesDescription', - constraint: { - doesNotContain: { - value: "Uncorrected", - }, - }, - required: false, + options: [], + id: 'displaySet', }, ], - studyMatchingRules: [ - - ], }, ], createdDate: '2021-02-23T18:32:42.850Z', }, ], - numberOfPriorsReferenced: 0, + numberOfPriorsReferenced: -1, }; function getHangingProtocolModule() { return [ { - name: hangingProtocolName, - protocols: [petCTProtocol], + name: 'default', + protocols: [defaultProtocol], }, ]; } diff --git a/extensions/default/src/getLayoutTemplateModule.js b/extensions/default/src/getLayoutTemplateModule.js index 793f002fa85..856d8d12ae1 100644 --- a/extensions/default/src/getLayoutTemplateModule.js +++ b/extensions/default/src/getLayoutTemplateModule.js @@ -5,11 +5,11 @@ import ViewerLayout from './ViewerLayout'; - Init layout based on the displaySets and the objects. */ -export default function ({ +export default function({ servicesManager, extensionManager, commandsManager, - hotkeysManager + hotkeysManager, }) { function ViewerLayoutWithServices(props) { return ViewerLayout({ diff --git a/extensions/default/src/getPTImageIdInstanceMetadata.ts b/extensions/default/src/getPTImageIdInstanceMetadata.ts new file mode 100644 index 00000000000..8d89507c4d6 --- /dev/null +++ b/extensions/default/src/getPTImageIdInstanceMetadata.ts @@ -0,0 +1,128 @@ +import OHIF from '@ohif/core'; + +import { + InstanceMetadata, + PhilipsPETPrivateGroup, +} from '@cornerstonejs/calculate-suv/src/types'; + +const metadataProvider = OHIF.classes.MetadataProvider; + +export default function getPTImageIdInstanceMetadata( + imageId: string +): InstanceMetadata { + const dicomMetaData = metadataProvider.get('instance', imageId); + + if (!dicomMetaData) { + throw new Error('dicom metadata are required'); + } + + if ( + dicomMetaData.SeriesDate === undefined || + dicomMetaData.SeriesTime === undefined || + dicomMetaData.PatientWeight === undefined || + dicomMetaData.CorrectedImage === undefined || + dicomMetaData.Units === undefined || + !dicomMetaData.RadiopharmaceuticalInformationSequence || + !dicomMetaData.RadiopharmaceuticalInformationSequence.length || + dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadionuclideHalfLife === undefined || + dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadionuclideTotalDose === undefined || + dicomMetaData.DecayCorrection === undefined || + dicomMetaData.AcquisitionDate === undefined || + dicomMetaData.AcquisitionTime === undefined || + (dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadiopharmaceuticalStartDateTime === undefined && + dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadiopharmaceuticalStartTime === undefined) + ) { + throw new Error('required metadata are missing'); + } + + const instanceMetadata: InstanceMetadata = { + CorrectedImage: dicomMetaData.CorrectedImage, + Units: dicomMetaData.Units, + RadionuclideHalfLife: + dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadionuclideHalfLife, + RadionuclideTotalDose: + dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadionuclideTotalDose, + RadiopharmaceuticalStartDateTime: + dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadiopharmaceuticalStartDateTime, + RadiopharmaceuticalStartTime: + dicomMetaData.RadiopharmaceuticalInformationSequence[0] + .RadiopharmaceuticalStartTime, + DecayCorrection: dicomMetaData.DecayCorrection, + PatientWeight: dicomMetaData.PatientWeight, + SeriesDate: dicomMetaData.SeriesDate, + SeriesTime: dicomMetaData.SeriesTime, + AcquisitionDate: dicomMetaData.AcquisitionDate, + AcquisitionTime: dicomMetaData.AcquisitionTime, + }; + + if ( + dicomMetaData['70531000'] || + dicomMetaData['70531000'] !== undefined || + dicomMetaData['70531009'] || + dicomMetaData['70531009'] !== undefined + ) { + const philipsPETPrivateGroup: PhilipsPETPrivateGroup = { + SUVScaleFactor: dicomMetaData['70531000'], + ActivityConcentrationScaleFactor: dicomMetaData['70531009'], + }; + instanceMetadata.PhilipsPETPrivateGroup = philipsPETPrivateGroup; + } + + if (dicomMetaData['0009100d'] && dicomMetaData['0009100d'] !== undefined) { + instanceMetadata.GEPrivatePostInjectionDateTime = dicomMetaData['0009100d']; + } + + if ( + dicomMetaData.FrameReferenceTime && + dicomMetaData.FrameReferenceTime !== undefined + ) { + instanceMetadata.FrameReferenceTime = dicomMetaData.FrameReferenceTime; + } + + if ( + dicomMetaData.ActualFrameDuration && + dicomMetaData.ActualFrameDuration !== undefined + ) { + instanceMetadata.ActualFrameDuration = dicomMetaData.ActualFrameDuration; + } + + if (dicomMetaData.PatientSex && dicomMetaData.PatientSex !== undefined) { + instanceMetadata.PatientSex = dicomMetaData.PatientSex; + } + + if (dicomMetaData.PatientSize && dicomMetaData.PatientSize !== undefined) { + instanceMetadata.PatientSize = dicomMetaData.PatientSize; + } + + return instanceMetadata; +} + +function convertInterfaceTimeToString(time): string { + const hours = `${time.hours || '00'}`.padStart(2, '0'); + const minutes = `${time.minutes || '00'}`.padStart(2, '0'); + const seconds = `${time.seconds || '00'}`.padStart(2, '0'); + + const fractionalSeconds = `${time.fractionalSeconds || '000000'}`.padEnd( + 6, + '0' + ); + + const timeString = `${hours}${minutes}${seconds}.${fractionalSeconds}`; + return timeString; +} + +function convertInterfaceDateToString(date): string { + const month = `${date.month}`.padStart(2, '0'); + const day = `${date.day}`.padStart(2, '0'); + const dateString = `${date.year}${month}${day}`; + return dateString; +} + +export { getPTImageIdInstanceMetadata }; diff --git a/extensions/default/src/getPanelModule.js b/extensions/default/src/getPanelModule.tsx similarity index 88% rename from extensions/default/src/getPanelModule.js rename to extensions/default/src/getPanelModule.tsx index 54f91a20ca2..f5eb53bf2a6 100644 --- a/extensions/default/src/getPanelModule.js +++ b/extensions/default/src/getPanelModule.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { WrappedPanelStudyBrowser } from './Panels'; - -import PanelMeasurementTable from './PanelMeasurementTable.js'; +import { WrappedPanelStudyBrowser, PanelMeasurementTable } from './Panels'; // TODO: // - No loading UI exists yet diff --git a/extensions/default/src/getSopClassHandlerModule.js b/extensions/default/src/getSopClassHandlerModule.js index 528a18aa153..e2dba9a6451 100644 --- a/extensions/default/src/getSopClassHandlerModule.js +++ b/extensions/default/src/getSopClassHandlerModule.js @@ -13,6 +13,8 @@ const makeDisplaySet = instances => { const instance = instances[0]; const imageSet = new ImageSet(instances); + const displayReconstructableInfo = isDisplaySetReconstructable(instances); + // set appropriate attributes to image set... imageSet.setAttributes({ displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID @@ -27,6 +29,7 @@ const makeDisplaySet = instances => { isMultiFrame: isMultiFrame(instance), numImageFrames: instances.length, SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, + isReconstructable: displayReconstructableInfo.value, }); // Sort the images in this series if needed diff --git a/extensions/default/src/getToolbarModule.js b/extensions/default/src/getToolbarModule.tsx similarity index 92% rename from extensions/default/src/getToolbarModule.js rename to extensions/default/src/getToolbarModule.tsx index 4d18776a951..260ef111bf7 100644 --- a/extensions/default/src/getToolbarModule.js +++ b/extensions/default/src/getToolbarModule.tsx @@ -1,7 +1,7 @@ import { ToolbarButton } from '@ohif/ui'; -import ToolbarDivider from './Toolbar/ToolbarDivider.jsx'; -import ToolbarLayoutSelector from './Toolbar/ToolbarLayoutSelector.jsx'; -import ToolbarSplitButton from './Toolbar/ToolbarSplitButton.jsx'; +import ToolbarDivider from './Toolbar/ToolbarDivider.tsx'; +import ToolbarLayoutSelector from './Toolbar/ToolbarLayoutSelector.tsx'; +import ToolbarSplitButton from './Toolbar/ToolbarSplitButton.tsx'; export default function getToolbarModule({ commandsManager, servicesManager }) { const toolbarService = servicesManager.services.ToolBarService; diff --git a/extensions/default/src/index.js b/extensions/default/src/index.js index 90937672c69..0cf1773a8a1 100644 --- a/extensions/default/src/index.js +++ b/extensions/default/src/index.js @@ -1,17 +1,22 @@ import getDataSourcesModule from './getDataSourcesModule.js'; import getLayoutTemplateModule from './getLayoutTemplateModule.js'; -import getPanelModule from './getPanelModule.js'; +import getPanelModule from './getPanelModule'; import getSopClassHandlerModule from './getSopClassHandlerModule.js'; import getHangingProtocolModule from './getHangingProtocolModule.js'; -import getToolbarModule from './getToolbarModule.js'; +import getToolbarModule from './getToolbarModule'; import commandsModule from './commandsModule'; +import getStudiesForPatientByStudyInstanceUID from './Panels/getStudiesForPatientByStudyInstanceUID'; import { id } from './id.js'; +import init from './init'; -export default { +const defaultExtension = { /** * Only required property. Should be a unique value across all extensions. */ id, + preRegistration: ({ servicesManager, configuration = {} }) => { + init({ servicesManager, configuration }); + }, getDataSourcesModule, getHangingProtocolModule, getLayoutTemplateModule, @@ -21,4 +26,16 @@ export default { getCommandsModule({ servicesManager, commandsManager }) { return commandsModule({ servicesManager, commandsManager }); }, + getUtilityModule({ servicesManager }) { + return [ + { + name: 'common', + exports: { + getStudiesForPatientByStudyInstanceUID, + }, + }, + ]; + }, }; + +export default defaultExtension; diff --git a/extensions/default/src/init.js b/extensions/default/src/init.js new file mode 100644 index 00000000000..89c0a3e132b --- /dev/null +++ b/extensions/default/src/init.js @@ -0,0 +1,70 @@ +import { DicomMetadataStore, classes } from '@ohif/core'; +import { calculateSUVScalingFactors } from '@cornerstonejs/calculate-suv'; + +import getPTImageIdInstanceMetadata from './getPTImageIdInstanceMetadata'; + +const metadataProvider = classes.MetadataProvider; + +/** + * + * @param {Object} servicesManager + * @param {Object} configuration + */ +export default function init({ servicesManager, configuration }) { + // Add + DicomMetadataStore.subscribe( + DicomMetadataStore.EVENTS.INSTANCES_ADDED, + handlePETImageMetadata + ); + + // If the metadata for PET has changed by the user (e.g. manually changing the PatientWeight) + // we need to recalculate the SUV Scaling Factors + DicomMetadataStore.subscribe( + DicomMetadataStore.EVENTS.SERIES_UPDATED, + handlePETImageMetadata + ); +} + +const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => { + const { instances } = DicomMetadataStore.getSeries( + StudyInstanceUID, + SeriesInstanceUID + ); + + const modality = instances[0].Modality; + if (modality !== 'PT') { + return; + } + const imageIds = instances.map(instance => instance.imageId); + const InstanceMetadataArray = []; + imageIds.forEach(imageId => { + const instanceMetadata = getPTImageIdInstanceMetadata(imageId); + if (instanceMetadata) { + InstanceMetadataArray.push(instanceMetadata); + } + }); + + if (!InstanceMetadataArray.length) { + return; + } + + // try except block to prevent errors when the metadata is not correct + let suvScalingFactors; + try { + suvScalingFactors = calculateSUVScalingFactors(InstanceMetadataArray); + } catch (error) { + console.log(error); + } + + if (!suvScalingFactors) { + return; + } + + InstanceMetadataArray.forEach((instanceMetadata, index) => { + metadataProvider.addCustomMetadata( + imageIds[index], + 'scalingModule', + suvScalingFactors[index] + ); + }); +}; diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json index cb5bfa0d1ff..31327fd583f 100644 --- a/extensions/dicom-pdf/package.json +++ b/extensions/dicom-pdf/package.json @@ -6,7 +6,7 @@ "license": "MIT", "repository": "OHIF/Viewers", "main": "dist/index.umd.js", - "module": "src/index.js", + "module": "src/index.tsx", "engines": { "node": ">=14", "npm": ">=6", @@ -21,7 +21,6 @@ }, "scripts": { "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo", - "dev:cornerstone": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package": "yarn run build", "start": "yarn run dev", @@ -31,16 +30,11 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "cornerstone-core": "^2.6.0", - "cornerstone-math": "^0.1.9", - "cornerstone-tools": "6.0.2", - "cornerstone-wado-image-loader": "4.0.4", - "dcmjs": "0.16.1", + "dcmjs": "^0.24.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", - "react": "^17.0.2", - "react-cornerstone-viewport": "4.1.2" + "react": "^17.0.2" }, "dependencies": { "@babel/runtime": "7.7.6", diff --git a/extensions/dicom-pdf/src/index.js b/extensions/dicom-pdf/src/index.tsx similarity index 95% rename from extensions/dicom-pdf/src/index.js rename to extensions/dicom-pdf/src/index.tsx index 94b8f13493a..dddcef665b9 100644 --- a/extensions/dicom-pdf/src/index.js +++ b/extensions/dicom-pdf/src/index.tsx @@ -19,7 +19,7 @@ const OHIFCornerstonePdfViewport = props => { /** * */ -export default { +const dicomPDFExtension = { /** * Only required property. Should be a unique value across all extensions. */ @@ -59,3 +59,5 @@ export default { }, getSopClassHandlerModule, }; + +export default dicomPDFExtension; diff --git a/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.js b/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.js deleted file mode 100644 index 590a33be226..00000000000 --- a/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; - -function OHIFCornerstonePdfViewport({ - displaySet, -}) { - const [url, setUrl] = useState(null); - const { pdfUrl } = displaySet; - useEffect(async () => { - setUrl(await pdfUrl); - }); - - return ( -
- -
No online PDF viewer installed
-
-
- ) -} - -OHIFCornerstonePdfViewport.propTypes = { - displaySet: PropTypes.object.isRequired, -}; - -export default OHIFCornerstonePdfViewport; diff --git a/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx b/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx new file mode 100644 index 00000000000..99488a21f37 --- /dev/null +++ b/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx @@ -0,0 +1,37 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; + +function OHIFCornerstonePdfViewport({ displaySets }) { + const [url, setUrl] = useState(null); + + if (displaySets && displaySets.length > 1) { + throw new Error( + 'OHIFCornerstonePdfViewport: only one display set is supported for dicom pdf right now' + ); + } + + const { pdfUrl } = displaySets[0]; + + useEffect(() => { + const load = async () => { + await pdfUrl; + setUrl(pdfUrl); + }; + + load(); + }, [pdfUrl]); + + return ( +
+ +
No online PDF viewer installed
+
+
+ ); +} + +OHIFCornerstonePdfViewport.propTypes = { + displaySets: PropTypes.arrayOf(PropTypes.object).isRequired, +}; + +export default OHIFCornerstonePdfViewport; diff --git a/extensions/dicom-sr/src/constants/toolNames.js b/extensions/dicom-sr/src/constants/toolNames.js deleted file mode 100644 index ba11ac0efab..00000000000 --- a/extensions/dicom-sr/src/constants/toolNames.js +++ /dev/null @@ -1,5 +0,0 @@ -const TOOL_NAMES = { - DICOM_SR_DISPLAY_TOOL: 'DICOMSRDisplayTool', -}; - -export default TOOL_NAMES; diff --git a/extensions/dicom-sr/src/init.js b/extensions/dicom-sr/src/init.js deleted file mode 100644 index 6e73d82da2e..00000000000 --- a/extensions/dicom-sr/src/init.js +++ /dev/null @@ -1,22 +0,0 @@ -import cornerstoneTools from 'cornerstone-tools'; -import dicomSRModule from './tools/modules/dicomSRModule'; -import { id } from './id'; - -import TOOL_NAMES from './constants/toolNames'; - -const defaultConfig = { - TOOL_NAMES: { - DICOM_SR_DISPLAY_TOOL: 'DICOMSRDisplayTool', - }, -}; - -/** - * @param {object} configuration - */ -export default function init({ configuration = {} }) { - const config = Object.assign({}, defaultConfig, configuration); - - TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = config.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL; - - cornerstoneTools.register('module', id, dicomSRModule); -} diff --git a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js deleted file mode 100644 index 6a35250e099..00000000000 --- a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js +++ /dev/null @@ -1,399 +0,0 @@ -import { importInternal, getToolState, toolColors } from 'cornerstone-tools'; -import { pixelToCanvas } from 'cornerstone-core'; - -import TOOL_NAMES from '../constants/toolNames'; -import SCOORD_TYPES from '../constants/scoordTypes'; -import { id } from '../id'; - -// Cornerstone 3rd party dev kit imports -const draw = importInternal('drawing/draw'); -const drawJoinedLines = importInternal('drawing/drawJoinedLines'); -const drawCircle = importInternal('drawing/drawCircle'); -const drawEllipse = importInternal('drawing/drawEllipse'); -const drawHandles = importInternal('drawing/drawHandles'); -const drawArrow = importInternal('drawing/drawArrow'); -const getNewContext = importInternal('drawing/getNewContext'); -const BaseTool = importInternal('base/BaseTool'); -const drawLinkedTextBox = importInternal('drawing/drawLinkedTextBox'); - -/** - * @class DICOMSRDisplayTool - Renders DICOMSR data in a read only manner (i.e. as an overlay). - * @extends cornerstoneTools.BaseTool - */ -export default class DICOMSRDisplayTool extends BaseTool { - constructor(props = {}) { - const defaultProps = { - mixins: ['enabledOrDisabledBinaryTool'], - name: TOOL_NAMES.DICOM_SR_DISPLAY_TOOL, - }; - - const initialProps = Object.assign(defaultProps, props); - - super(initialProps); - - this._module = cornerstoneTools.getModule(id); - } - - renderToolData(evt) { - const eventData = evt.detail; - const { element } = eventData; - const module = this._module; - - const toolState = getToolState(element, this.name); - - if (!toolState) { - return; - } - - const trackingUniqueIdentifiersForElement = module.getters.trackingUniqueIdentifiersForElement( - element - ); - - const { - activeIndex, - trackingUniqueIdentifiers, - } = trackingUniqueIdentifiersForElement; - - const activeTrackingUniqueIdentifier = - trackingUniqueIdentifiers[activeIndex]; - - // Filter toolData to only render the data for the active SR. - const filteredToolData = toolState.data.filter(td => - trackingUniqueIdentifiers.includes(td.TrackingUniqueIdentifier) - ); - - let shouldRepositionTextBoxes = false; - - for (let i = 0; i < filteredToolData.length; i++) { - const data = filteredToolData[i]; - const { renderableData, labels } = data; - - const color = - data.TrackingUniqueIdentifier === activeTrackingUniqueIdentifier - ? toolColors.getActiveColor() - : toolColors.getToolColor(); - const lineWidth = 2; - const options = { - color, - lineWidth, - handleRadius: 6, - }; - - Object.keys(renderableData).forEach(GraphicType => { - const renderableDataForGraphicType = renderableData[GraphicType]; - - switch (GraphicType) { - case SCOORD_TYPES.POINT: - this.renderPoint(renderableDataForGraphicType, eventData, options); - break; - case SCOORD_TYPES.MULTIPOINT: - this.renderMultipoint( - renderableDataForGraphicType, - eventData, - options - ); - break; - case SCOORD_TYPES.POLYLINE: - this.renderPolyLine( - renderableDataForGraphicType, - eventData, - options - ); - break; - case SCOORD_TYPES.CIRCLE: - this.renderCircle(renderableDataForGraphicType, eventData, options); - break; - case SCOORD_TYPES.ELLIPSE: - this.renderEllipse( - renderableDataForGraphicType, - eventData, - options - ); - break; - } - }); - - const { element } = eventData; - const context = getNewContext(eventData.canvasContext.canvas); - - if (!data.handles || !data.handles.textBox) { - const textBox = { - active: false, - hasMoved: true, - movesIndependently: false, - drawnIndependently: true, - allowedOutsideImage: true, - hasBoundingBox: true, - }; - - const anchorPoints = _getTextBoxAnchorPointsForRenderableData( - renderableData, - eventData - ); - textBox.anchorPoints = anchorPoints; - - const bottomRight = { - x: Math.max(...anchorPoints.map(point => point.x)), - y: Math.max(...anchorPoints.map(point => point.y)), - }; - - textBox.x = bottomRight.x; - textBox.y = bottomRight.y; - - data.handles = {}; - data.handles.textBox = textBox; - - shouldRepositionTextBoxes = true; - } - - const text = _getTextBoxLinesFromLabels(labels); - - function textBoxAnchorPoints() { - return data.handles.textBox.anchorPoints; - } - - draw(context, context => { - drawLinkedTextBox( - context, - element, - data.handles.textBox, - text, - data.handles, - textBoxAnchorPoints, - color, - lineWidth, - 0, - true - ); - }); - } - - // TOOD -> text boxes may overlap with other annotations at the moment. - // To be fixed after we get requirements. - // if (shouldRepositionTextBoxes) { - // this.repositionTextBox(filteredToolData, eventData); - // } - } - - // repositionTextBox(toolData, eventData) { - // const toolBoundingBoxes = []; - - // for (let i = 0; i < toolData.length; i++) { - // const toolDataI = toolData[i]; - - // const { textBox } = toolDataI.handles; - // const { anchorPoints } = textBox; - - // const boundingBox = _getBoundingBoxFromAnchorPoints(anchorPoints); - // // Get the textbox bounding locations. - // // Get the tool extents. - // } - // } - - renderPolyLine(renderableData, eventData, options) { - const { element } = eventData; - const context = getNewContext(eventData.canvasContext.canvas); - - renderableData.forEach(points => { - draw(context, context => { - drawJoinedLines(context, element, points[0], points, options); - }); - }); - } - - renderMultipoint(renderableData, eventData, options) { - const context = getNewContext(eventData.canvasContext.canvas); - - renderableData.forEach(points => { - draw(context, context => { - drawHandles(context, eventData, points, options); - }); - }); - } - - renderPoint(renderableData, eventData, options) { - // Render single point as an arrow. - const { element, image } = eventData; - const { rows, columns } = image; - const context = getNewContext(eventData.canvasContext.canvas); - - const { color, lineWidth } = options; - - // Find a suitable length for the image size. - - const xOffset = columns / 10; - const yOffset = rows / 10; - - renderableData.forEach(points => { - const point = points[0]; // The SCOORD type is POINT so the array length is 1. - draw(context, context => { - // Draw the arrow - const handleStartCanvas = pixelToCanvas(element, point); - const handleEndCanvas = pixelToCanvas(element, { - x: point.x + xOffset, - y: point.y + yOffset, - }); - - drawArrow( - context, - handleEndCanvas, - handleStartCanvas, - color, - lineWidth, - false - ); - }); - }); - } - - renderCircle(renderableData, eventData, options) { - const { element } = eventData; - - const context = getNewContext(eventData.canvasContext.canvas); - - renderableData.forEach(circle => { - const { center, radius } = circle; - - drawCircle(context, element, center, radius, options); - }); - } - - renderEllipse(renderableData, eventData, options) { - const { element } = eventData; - - const context = getNewContext(eventData.canvasContext.canvas); - - renderableData.forEach(ellipse => { - const { corner1, corner2 } = ellipse; - - drawEllipse( - context, - element, - corner1, - corner2, - options, - 'pixel', - 0 // TODO -> Work our the initial rotation and add it here so we render appropriately rotated ellipses. - ); - }); - } -} - -function _getTextBoxLinesFromLabels(labels) { - // TODO -> max 3 for now (label + shortAxis + longAxis), need a generic solution for this! - - const labelLength = Math.min(labels.length, 3); - const lines = []; - - for (let i = 0; i < labelLength; i++) { - const labelEntry = labels[i]; - lines.push(`${_labelToShorthand(labelEntry.label)}${labelEntry.value}`); - } - - return lines; -} - -const SHORT_HAND_MAP = { - 'Short Axis': 'W ', - 'Long Axis': 'L ', - AREA: 'Area ', - Length: '', - CORNERSTONEFREETEXT: '', -}; - -function _labelToShorthand(label) { - const shortHand = SHORT_HAND_MAP[label]; - - if (shortHand !== undefined) { - return shortHand; - } - - return label; -} - -function _getTextBoxAnchorPointsForRenderableData(renderableData, eventData) { - let anchorPoints = []; - - Object.keys(renderableData).forEach(GraphicType => { - const renderableDataForGraphicType = renderableData[GraphicType]; - - switch (GraphicType) { - case SCOORD_TYPES.POINT: - renderableDataForGraphicType.forEach(points => { - anchorPoints = [...anchorPoints, ...points]; - - // Add other arrow point based on image size. - const { image } = eventData; - const { rows, columns } = image; - - const xOffset = columns / 10; - const yOffset = rows / 10; - const point = points[0]; - - anchorPoints.push({ x: point.x + xOffset, y: point.y + yOffset }); - }); - - break; - case SCOORD_TYPES.MULTIPOINT: - case SCOORD_TYPES.POLYLINE: - renderableDataForGraphicType.forEach(points => { - anchorPoints = [...anchorPoints, ...points]; - }); - break; - case SCOORD_TYPES.CIRCLE: - renderableDataForGraphicType.forEach(circle => { - const { center, radius } = circle; - - anchorPoints.push({ x: center.x + radius, y: center.y }); - anchorPoints.push({ x: center.x - radius, y: center.y }); - anchorPoints.push({ x: center.x, y: center.y + radius }); - anchorPoints.push({ x: center.x, y: center.y - radius }); - }); - - break; - case SCOORD_TYPES.ELLIPSE: - renderableDataForGraphicType.forEach(ellipse => { - const { corner1, corner2 } = ellipse; - - const halfWidth = Math.abs(corner1.x - corner2.x) / 2; - const halfHeight = Math.abs(corner1.y - corner2.y) / 2; - - const center = { - x: (corner1.x + corner2.x) / 2, - y: (corner1.y + corner2.y) / 2, - }; - - anchorPoints.push({ x: center.x + halfWidth, y: center.y }); - anchorPoints.push({ x: center.x - halfWidth, y: center.y }); - anchorPoints.push({ x: center.x, y: center.y + halfHeight }); - anchorPoints.push({ x: center.x, y: center.y - halfHeight }); - }); - break; - } - }); - - return anchorPoints; -} - -function _getBoundingBoxFromAnchorPoints(anchorPoints) { - let minX = Infinity; - let maxX = -Infinity; - let minY = Infinity; - let maxY = -Infinity; - - anchorPoints.forEach(point => { - const { x, y } = point; - - if (x > maxX) { - maxX = x; - } else if (x < minX) { - minX = x; - } - - if (y > maxX) { - maxY = y; - } else if (y < minY) { - minY = y; - } - }); -} diff --git a/extensions/dicom-sr/src/tools/modules/dicomSRModule.js b/extensions/dicom-sr/src/tools/modules/dicomSRModule.js deleted file mode 100644 index 3fbb3c36054..00000000000 --- a/extensions/dicom-sr/src/tools/modules/dicomSRModule.js +++ /dev/null @@ -1,61 +0,0 @@ -import cornerstone from 'cornerstone-core'; - -const state = { - TrackingUniqueIdentifier: null, - trackingIdentifiersByEnabledElementUUID: {}, -}; - -function setTrackingUniqueIdentifiersForElement( - element, - trackingUniqueIdentifiers, - activeIndex = 0 -) { - const enabledElement = cornerstone.getEnabledElement(element); - const { uuid } = enabledElement; - - state.trackingIdentifiersByEnabledElementUUID[uuid] = { - trackingUniqueIdentifiers, - activeIndex, - }; -} - -function setActiveTrackingUniqueIdentifierForElement( - element, - TrackingUniqueIdentifier -) { - const enabledElement = cornerstone.getEnabledElement(element); - const { uuid } = enabledElement; - - const trackingIdentifiersForElement = - state.trackingIdentifiersByEnabledElementUUID[uuid]; - - if (trackingIdentifiersForElement) { - const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex( - tuid => tuid === TrackingUniqueIdentifier - ); - - trackingIdentifiersForElement.activeIndex = activeIndex; - } -} - -function getTrackingUniqueIdentifiersForElement(element) { - const enabledElement = cornerstone.getEnabledElement(element); - const { uuid } = enabledElement; - - if (state.trackingIdentifiersByEnabledElementUUID[uuid]) { - return state.trackingIdentifiersByEnabledElementUUID[uuid]; - } - - return { trackingUniqueIdentifiers: [] }; -} - -export default { - state, - getters: { - trackingUniqueIdentifiersForElement: getTrackingUniqueIdentifiersForElement, - }, - setters: { - trackingUniqueIdentifiersForElement: setTrackingUniqueIdentifiersForElement, - activeTrackingUniqueIdentifierForElement: setActiveTrackingUniqueIdentifierForElement, - }, -}; diff --git a/extensions/dicom-sr/src/utils/addMeasurement.js b/extensions/dicom-sr/src/utils/addMeasurement.js deleted file mode 100644 index 8d592e7f82c..00000000000 --- a/extensions/dicom-sr/src/utils/addMeasurement.js +++ /dev/null @@ -1,138 +0,0 @@ -import cornerstoneTools from 'cornerstone-tools'; -import cornerstoneMath from 'cornerstone-math'; -import cornerstone from 'cornerstone-core'; -import TOOL_NAMES from '../constants/toolNames'; -import SCOORD_TYPES from '../constants/scoordTypes'; - -const globalImageIdSpecificToolStateManager = - cornerstoneTools.globalImageIdSpecificToolStateManager; - -export default function addMeasurement( - measurement, - imageId, - displaySetInstanceUID -) { - // TODO -> Render rotated ellipse . - - const toolName = TOOL_NAMES.DICOM_SR_DISPLAY_TOOL; - - const measurementData = { - TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier, - renderableData: {}, - labels: measurement.labels, - }; - - measurement.coords.forEach(coord => { - const { GraphicType, GraphicData } = coord; - - if (measurementData.renderableData[GraphicType] === undefined) { - measurementData.renderableData[GraphicType] = []; - } - - measurementData.renderableData[GraphicType].push( - _getRenderableData(GraphicType, GraphicData) - ); - }); - - const toolState = globalImageIdSpecificToolStateManager.saveToolState(); - - if (toolState[imageId] === undefined) { - toolState[imageId] = {}; - } - - const imageIdToolState = toolState[imageId]; - - // If we don't have tool state for this type of tool, add an empty object - if (imageIdToolState[toolName] === undefined) { - imageIdToolState[toolName] = { - data: [], - }; - } - - const toolData = imageIdToolState[toolName]; - - toolData.data.push(measurementData); - - measurement.loaded = true; - measurement.imageId = imageId; - measurement.displaySetInstanceUID = displaySetInstanceUID; - - // Remove the unneeded coord now its processed, but keep the SOPInstanceUID. - // NOTE: We assume that each SCOORD in the MeasurementGroup maps onto one frame, - // It'd be super werid if it didn't anyway as a SCOORD. - measurement.ReferencedSOPInstanceUID = - measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID; - delete measurement.coords; -} - -function _getRenderableData(GraphicType, GraphicData) { - let renderableData; - - switch (GraphicType) { - case SCOORD_TYPES.POINT: - case SCOORD_TYPES.MULTIPOINT: - case SCOORD_TYPES.POLYLINE: - renderableData = []; - - for (let i = 0; i < GraphicData.length; i += 2) { - renderableData.push({ x: GraphicData[i], y: GraphicData[i + 1] }); - } - break; - case SCOORD_TYPES.CIRCLE: - const center = { x: GraphicData[0], y: GraphicData[1] }; - const onPerimeter = { x: GraphicData[2], y: GraphicData[3] }; - - const radius = cornerstoneMath.point.distance(center, onPerimeter); - - renderableData = { - center, - radius, - }; - break; - case SCOORD_TYPES.ELLIPSE: - console.warn('ROTATED ELLIPSE NOT YET SUPPORTED!'); - - const majorAxis = [ - { x: GraphicData[0], y: GraphicData[1] }, - { x: GraphicData[2], y: GraphicData[3] }, - ]; - const minorAxis = [ - { x: GraphicData[4], y: GraphicData[5] }, - { x: GraphicData[6], y: GraphicData[7] }, - ]; - - // Calculate two opposite corners of box defined by two axes. - - const minorAxisLength = cornerstoneMath.point.distance( - minorAxis[0], - minorAxis[1] - ); - - const minorAxisDirection = { - x: (minorAxis[1].x - minorAxis[0].x) / minorAxisLength, - y: (minorAxis[1].y - minorAxis[0].y) / minorAxisLength, - }; - - const halfMinorAxisLength = minorAxisLength / 2; - - // First end point of major axis + half minor axis vector - const corner1 = { - x: majorAxis[0].x + minorAxisDirection.x * halfMinorAxisLength, - y: majorAxis[0].y + minorAxisDirection.y * halfMinorAxisLength, - }; - - // Second end point of major axis - half of minor axis vector - const corner2 = { - x: majorAxis[1].x - minorAxisDirection.x * halfMinorAxisLength, - y: majorAxis[1].y - minorAxisDirection.y * halfMinorAxisLength, - }; - - renderableData = { - corner1, - corner2, - }; - break; - } - - return renderableData; -} diff --git a/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js b/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js deleted file mode 100644 index 56242fcf669..00000000000 --- a/extensions/dicom-sr/src/viewports/OHIFCornerstoneSRViewport.js +++ /dev/null @@ -1,570 +0,0 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import cornerstoneTools from 'cornerstone-tools'; -import cornerstone from 'cornerstone-core'; -import CornerstoneViewport from 'react-cornerstone-viewport'; -import OHIF, { DicomMetadataStore, utils } from '@ohif/core'; -import DICOMSRDisplayTool from './../tools/DICOMSRDisplayTool'; -import ViewportOverlay from './ViewportOverlay'; -import { - Notification, - ViewportActionBar, - useViewportGrid, - useViewportDialog, -} from '@ohif/ui'; -import TOOL_NAMES from './../constants/toolNames'; -import { adapters } from 'dcmjs'; -import { id } from './../id'; - -const { formatDate } = utils; -const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex'); -const globalImageIdSpecificToolStateManager = - cornerstoneTools.globalImageIdSpecificToolStateManager; - -const { StackManager, guid } = OHIF.utils; - -const MEASUREMENT_TRACKING_EXTENSION_ID = - '@ohif/extension-measurement-tracking'; - -function OHIFCornerstoneSRViewport({ - children, - dataSource, - displaySet, - viewportIndex, - servicesManager, - extensionManager, -}) { - const { - DisplaySetService, - MeasurementService, - ToolBarService, - } = servicesManager.services; - const [viewportGrid, viewportGridService] = useViewportGrid(); - const [viewportDialogState, viewportDialogApi] = useViewportDialog(); - const [measurementSelected, setMeasurementSelected] = useState(0); - const [measurementCount, setMeasurementCount] = useState(1); - const [viewportData, setViewportData] = useState(null); - const [activeDisplaySetData, setActiveDisplaySetData] = useState({}); - const [element, setElement] = useState(null); - const [isHydrated, setIsHydrated] = useState(displaySet.isHydrated); - const { viewports, activeViewportIndex } = viewportGrid; - - useEffect(() => { - const onDisplaySetsRemovedSubscription = DisplaySetService.subscribe( - DisplaySetService.EVENTS.DISPLAY_SETS_REMOVED, - ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { - viewportGridService.setDisplaysetForViewport({ - viewportIndex: activeViewportIndex, - displaySetInstanceUID: undefined, - }); - } - } - ); - - return () => { - onDisplaySetsRemovedSubscription.unsubscribe(); - }; - }, []); - - // Optional hook into tracking extension, if present. - let trackedMeasurements; - let sendTrackedMeasurementsEvent; - - // TODO: this is a hook that fails if we register/de-register - // - if ( - extensionManager.registeredExtensionIds.includes( - MEASUREMENT_TRACKING_EXTENSION_ID - ) - ) { - const contextModule = extensionManager.getModuleEntry( - '@ohif/extension-measurement-tracking.contextModule.TrackedMeasurementsContext' - ); - - const useTrackedMeasurements = () => useContext(contextModule.context); - - [ - trackedMeasurements, - sendTrackedMeasurementsEvent, - ] = useTrackedMeasurements(); - } - - // Locked if tracking any series - let isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0; - useEffect(() => { - isLocked = trackedMeasurements?.context?.trackedSeries?.length > 0; - }, [trackedMeasurements]); - - function _getToolAlias() { - const primaryToolId = ToolBarService.state.primaryToolId; - let toolAlias = primaryToolId; - - switch (primaryToolId) { - case 'Length': - toolAlias = 'SRLength'; - break; - case 'Bidirectional': - toolAlias = 'SRBidirectional'; - break; - case 'ArrowAnnotate': - toolAlias = 'SRArrowAnnotate'; - break; - case 'EllipticalRoi': - toolAlias = 'SREllipticalRoi'; - break; - } - - return toolAlias; - } - - const onElementEnabled = evt => { - const eventData = evt.detail; - const targetElement = eventData.element; - const toolAlias = _getToolAlias(); // These are 1:1 for built-in only - - // ~~ MAGIC - cornerstoneTools.addToolForElement(targetElement, DICOMSRDisplayTool); - cornerstoneTools.setToolEnabledForElement( - targetElement, - TOOL_NAMES.DICOM_SR_DISPLAY_TOOL - ); - - // ~~ Variants - cornerstoneTools.addToolForElement( - targetElement, - cornerstoneTools.LengthTool, - { - name: 'SRLength', - configuration: { - renderDashed: true, - }, - } - ); - cornerstoneTools.addToolForElement( - targetElement, - cornerstoneTools.ArrowAnnotateTool, - { - name: 'SRArrowAnnotate', - configuration: { - renderDashed: true, - }, - } - ); - cornerstoneTools.addToolForElement( - targetElement, - cornerstoneTools.BidirectionalTool, - { - name: 'SRBidirectional', - configuration: { - renderDashed: true, - }, - } - ); - cornerstoneTools.addToolForElement( - targetElement, - cornerstoneTools.EllipticalRoiTool, - { - name: 'SREllipticalRoi', - configuration: { - renderDashed: true, - }, - } - ); - - // ~~ Business as usual - cornerstoneTools.setToolActiveForElement(targetElement, 'PanMultiTouch', { - pointers: 2, - }); - cornerstoneTools.setToolActiveForElement( - targetElement, - 'ZoomTouchPinch', - {} - ); - - // TODO: Add always dashed tool alternative aliases - // TODO: or same name... alternative config? - cornerstoneTools.setToolActiveForElement(targetElement, toolAlias, { - mouseButtonMask: 1, - }); - cornerstoneTools.setToolActiveForElement(targetElement, 'Pan', { - mouseButtonMask: 4, - }); - cornerstoneTools.setToolActiveForElement(targetElement, 'Zoom', { - mouseButtonMask: 2, - }); - cornerstoneTools.setToolActiveForElement( - targetElement, - 'StackScrollMouseWheel', - {} - ); - - setTrackingUniqueIdentifiersForElement(targetElement); - setElement(targetElement); - - // TODO: Enabled Element appears to be incorrect here, it should be called - // 'element' since it is the DOM element, not the enabledElement object - const OHIFCornerstoneEnabledElementEvent = new CustomEvent( - 'ohif-cornerstone-enabled-element-event', - { - detail: { - context: 'ACTIVE_VIEWPORT::STRUCTURED_REPORT', - enabledElement: targetElement, - viewportIndex, - }, - } - ); - - document.dispatchEvent(OHIFCornerstoneEnabledElementEvent); - }; - - useEffect(() => { - if (!displaySet.isLoaded) { - displaySet.load(); - } - setIsHydrated(displaySet.isHydrated); - }, [displaySet]); - - const setTrackingUniqueIdentifiersForElement = useCallback(targetElement => { - const { measurements } = displaySet; - - const srModule = cornerstoneTools.getModule(id); - - srModule.setters.trackingUniqueIdentifiersForElement( - targetElement, - measurements.map(measurement => measurement.TrackingUniqueIdentifier), - measurementSelected - ); - }); - - useEffect(() => { - const numMeasurements = displaySet.measurements.length; - - setMeasurementCount(numMeasurements); - }, [dataSource, displaySet]); - - const updateViewport = useCallback(newMeasurementSelected => { - const { - StudyInstanceUID, - displaySetInstanceUID, - sopClassUids, - } = displaySet; - - if (!StudyInstanceUID || !displaySetInstanceUID) { - return; - } - - if (sopClassUids && sopClassUids.length > 1) { - console.warn( - 'More than one SOPClassUID in the same series is not yet supported.' - ); - } - - _getViewportAndActiveDisplaySetData( - dataSource, - displaySet, - newMeasurementSelected, - DisplaySetService, - element - ).then(({ viewportData, activeDisplaySetData }) => { - setViewportData({ ...viewportData }); - setActiveDisplaySetData({ ...activeDisplaySetData }); - setMeasurementSelected(newMeasurementSelected); - - if (element !== null) { - scrollToIndex(element, viewportData.stack.currentImageIdIndex); - cornerstone.updateImage(element); - } - }); - }); - - useEffect( - () => { - if (element !== null) { - setTrackingUniqueIdentifiersForElement(element); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [dataSource, displaySet] - ); - - useEffect( - () => { - updateViewport(measurementSelected); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [dataSource, displaySet, element] - ); - - const firstViewportIndexWithMatchingDisplaySetUid = viewports.findIndex( - vp => vp.displaySetInstanceUID === displaySet.displaySetInstanceUID - ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - let childrenWithProps = null; - - if (!viewportData) { - return null; - } - - const { - imageIds, - currentImageIdIndex, - // If this comes from the instance, would be a better default - // `FrameTime` in the instance - // frameRate = 0, - } = viewportData.stack; - - if (children && children.length) { - childrenWithProps = children.map((child, index) => { - return ( - child && - React.cloneElement(child, { - viewportIndex, - key: index, - }) - ); - }); - } - - const { Modality } = displaySet; - - const { - PatientID, - PatientName, - PatientSex, - PatientAge, - SliceThickness, - ManufacturerModelName, - StudyDate, - SeriesDescription, - SeriesInstanceUID, - SpacingBetweenSlices, - SeriesNumber, - displaySetInstanceUID, - } = activeDisplaySetData; - - const onMeasurementChange = direction => { - let newMeasurementSelected = measurementSelected; - - if (direction === 'right') { - newMeasurementSelected++; - - if (newMeasurementSelected >= measurementCount) { - newMeasurementSelected = 0; - } - } else { - newMeasurementSelected--; - - if (newMeasurementSelected < 0) { - newMeasurementSelected = measurementCount - 1; - } - } - - updateViewport(newMeasurementSelected); - }; - - const label = viewports.length > 1 ? _viewportLabels[viewportIndex] : ''; - - // TODO -> disabled double click for now: onDoubleClick={_onDoubleClick} - return ( - <> - { - evt.stopPropagation(); - evt.preventDefault(); - }} - onPillClick={() => { - sendTrackedMeasurementsEvent('RESTORE_PROMPT_HYDRATE_SR', { - displaySetInstanceUID: displaySet.displaySetInstanceUID, - viewportIndex, - }); - }} - onSeriesChange={onMeasurementChange} - studyData={{ - label, - useAltStyling: true, - isTracked: false, - isLocked, - isRehydratable: displaySet.isRehydratable, - isHydrated, - studyDate: formatDate(StudyDate), - currentSeries: SeriesNumber, - seriesDescription: SeriesDescription, - modality: Modality, - patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', - patientSex: PatientSex || '', - patientAge: PatientAge || '', - MRN: PatientID || '', - thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', - spacing: - SpacingBetweenSlices !== undefined - ? `${SpacingBetweenSlices.toFixed(2)}mm` - : '', - scanner: ManufacturerModelName || '', - }, - }} - /> - -
- { - return ( - - ); - }} - /> -
- {viewportDialogState.viewportIndex === viewportIndex && ( - - )} -
- {childrenWithProps} -
- - ); -} - -const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; - -OHIFCornerstoneSRViewport.propTypes = { - displaySet: PropTypes.object.isRequired, - viewportIndex: PropTypes.number.isRequired, - dataSource: PropTypes.object, - children: PropTypes.node, - customProps: PropTypes.object, -}; - -OHIFCornerstoneSRViewport.defaultProps = { - customProps: {}, -}; - -/** - * Obtain the CornerstoneTools Stack for the specified display set. - * - * @param {Object} displaySet - * @param {Object} dataSource - * @return {Object} CornerstoneTools Stack - */ -function _getCornerstoneStack( - measurement, - dataSource, - DisplaySetService, - element -) { - const { displaySetInstanceUID, TrackingUniqueIdentifier } = measurement; - - const displaySet = DisplaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); - - // Get stack from Stack Manager - const storedStack = StackManager.findOrCreateStack(displaySet, dataSource); - - // Clone the stack here so we don't mutate it - const stack = Object.assign({}, storedStack); - - const { imageId } = measurement; - - stack.currentImageIdIndex = stack.imageIds.findIndex(i => i === imageId); - - if (element) { - const srModule = cornerstoneTools.getModule(id); - - srModule.setters.activeTrackingUniqueIdentifierForElement( - element, - TrackingUniqueIdentifier - ); - } - - return stack; -} - -async function _getViewportAndActiveDisplaySetData( - dataSource, - displaySet, - measurementSelected, - DisplaySetService, - element -) { - let viewportData; - - const { measurements } = displaySet; - const measurement = measurements[measurementSelected]; - - const stack = _getCornerstoneStack( - measurement, - dataSource, - DisplaySetService, - element - ); - - viewportData = { - StudyInstanceUID: displaySet.StudyInstanceUID, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - stack, - }; - - const { displaySetInstanceUID } = measurement; - - const referencedDisplaySet = DisplaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); - - const image0 = referencedDisplaySet.images[0]; - const activeDisplaySetData = { - PatientID: image0.PatientID, - PatientName: image0.PatientName, - PatientSex: image0.PatientSex, - PatientAge: image0.PatientAge, - SliceThickness: image0.SliceThickness, - StudyDate: image0.StudyDate, - SeriesDescription: image0.SeriesDescription, - SeriesInstanceUID: image0.SeriesInstanceUID, - SeriesNumber: image0.SeriesNumber, - ManufacturerModelName: image0.ManufacturerModelName, - SpacingBetweenSlices: image0.SpacingBetweenSlices, - displaySetInstanceUID, - }; - - return { viewportData, activeDisplaySetData }; -} - -function _onDoubleClick() { - const cancelActiveManipulatorsForElement = cornerstoneTools.getModule( - 'manipulatorState' - ).setters.cancelActiveManipulatorsForElement; - const enabledElements = cornerstoneTools.store.state.enabledElements; - enabledElements.forEach(element => { - cancelActiveManipulatorsForElement(element); - }); -} - -export default OHIFCornerstoneSRViewport; diff --git a/extensions/dicom-sr/src/viewports/ViewportOverlay.js b/extensions/dicom-sr/src/viewports/ViewportOverlay.js deleted file mode 100644 index 045770d9055..00000000000 --- a/extensions/dicom-sr/src/viewports/ViewportOverlay.js +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cornerstone from 'cornerstone-core'; -import classnames from 'classnames'; - -const ViewportOverlay = ({ - imageId, - scale, - windowWidth, - windowCenter, - imageIndex, - stackSize, - activeTools, -}) => { - const topLeft = 'top-viewport left-viewport'; - const topRight = 'top-viewport right-viewport-scrollbar'; - const bottomRight = 'bottom-viewport right-viewport-scrollbar'; - const bottomLeft = 'bottom-viewport left-viewport'; - const overlay = 'absolute pointer-events-none'; - - const isZoomActive = activeTools.includes('Zoom'); - const isWwwcActive = activeTools.includes('Wwwc'); - - if (!imageId) { - return null; - } - - const generalImageModule = - cornerstone.metaData.get('generalImageModule', imageId) || {}; - const { instanceNumber } = generalImageModule; - - return ( -
-
- {isZoomActive && ( -
- Zoom: - {scale.toFixed(2)}x -
- )} - {isWwwcActive && ( -
- W: - - {windowWidth.toFixed(0)} - - L: - {windowCenter.toFixed(0)} -
- )} -
-
- {stackSize > 1 && ( -
- I: - - {`${instanceNumber} (${imageIndex}/${stackSize})`} - -
- )} -
-
-
-
- ); -}; - -ViewportOverlay.defaultProps = { - stackSize: 99999, -}; - -ViewportOverlay.propTypes = { - scale: PropTypes.number.isRequired, - windowWidth: PropTypes.number.isRequired, - windowCenter: PropTypes.number.isRequired, - imageId: PropTypes.string.isRequired, - imageIndex: PropTypes.number.isRequired, - stackSize: PropTypes.number.isRequired, - activeTools: PropTypes.arrayOf(PropTypes.string), -}; - -ViewportOverlay.defaultProps = { - activeTools: [], -}; - -export default ViewportOverlay; diff --git a/extensions/dicom-video/.webpack/webpack.prod.js b/extensions/dicom-video/.webpack/webpack.prod.js index 61802e5cc8e..946363c5366 100644 --- a/extensions/dicom-video/.webpack/webpack.prod.js +++ b/extensions/dicom-video/.webpack/webpack.prod.js @@ -1,7 +1,7 @@ const webpack = require('webpack'); const merge = require('webpack-merge'); const path = require('path'); -const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); const pkg = require('./../package.json'); const ROOT_DIR = path.join(__dirname, './..'); @@ -12,7 +12,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/extensions/dicom-video/package.json b/extensions/dicom-video/package.json index fe394cb9c4b..c5a13b14ad7 100644 --- a/extensions/dicom-video/package.json +++ b/extensions/dicom-video/package.json @@ -6,7 +6,7 @@ "license": "MIT", "repository": "OHIF/Viewers", "main": "dist/index.umd.js", - "module": "src/index.js", + "module": "src/index.tsx", "engines": { "node": ">=14", "npm": ">=6", @@ -21,7 +21,6 @@ }, "scripts": { "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo", - "dev:cornerstone": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package": "yarn run build", "start": "yarn run dev", @@ -29,18 +28,13 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "^0.50.0", + "@ohif/core": "^3.0.0", "@ohif/ui": "^2.0.0", - "cornerstone-core": "^2.6.0", - "cornerstone-math": "^0.1.9", - "cornerstone-tools": "6.0.2", - "cornerstone-wado-image-loader": "4.0.4", - "dcmjs": "0.16.1", + "dcmjs": "^0.24.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", - "react": "^17.0.2", - "react-cornerstone-viewport": "4.1.2" + "react": "^17.0.2" }, "dependencies": { "@babel/runtime": "7.7.6", diff --git a/extensions/dicom-video/src/index.js b/extensions/dicom-video/src/index.tsx similarity index 96% rename from extensions/dicom-video/src/index.js rename to extensions/dicom-video/src/index.tsx index fab345e7655..17b4e5decb3 100644 --- a/extensions/dicom-video/src/index.js +++ b/extensions/dicom-video/src/index.tsx @@ -19,7 +19,7 @@ const OHIFCornerstoneVideoViewport = props => { /** * */ -export default { +const dicomVideoExtension = { /** * Only required property. Should be a unique value across all extensions. */ @@ -83,3 +83,5 @@ function _getToolAlias(toolName) { return toolAlias; } + +export default dicomVideoExtension; diff --git a/extensions/dicom-video/src/tools/modules/dicomVideoModule.js b/extensions/dicom-video/src/tools/modules/dicomVideoModule.js deleted file mode 100644 index 3fbb3c36054..00000000000 --- a/extensions/dicom-video/src/tools/modules/dicomVideoModule.js +++ /dev/null @@ -1,61 +0,0 @@ -import cornerstone from 'cornerstone-core'; - -const state = { - TrackingUniqueIdentifier: null, - trackingIdentifiersByEnabledElementUUID: {}, -}; - -function setTrackingUniqueIdentifiersForElement( - element, - trackingUniqueIdentifiers, - activeIndex = 0 -) { - const enabledElement = cornerstone.getEnabledElement(element); - const { uuid } = enabledElement; - - state.trackingIdentifiersByEnabledElementUUID[uuid] = { - trackingUniqueIdentifiers, - activeIndex, - }; -} - -function setActiveTrackingUniqueIdentifierForElement( - element, - TrackingUniqueIdentifier -) { - const enabledElement = cornerstone.getEnabledElement(element); - const { uuid } = enabledElement; - - const trackingIdentifiersForElement = - state.trackingIdentifiersByEnabledElementUUID[uuid]; - - if (trackingIdentifiersForElement) { - const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex( - tuid => tuid === TrackingUniqueIdentifier - ); - - trackingIdentifiersForElement.activeIndex = activeIndex; - } -} - -function getTrackingUniqueIdentifiersForElement(element) { - const enabledElement = cornerstone.getEnabledElement(element); - const { uuid } = enabledElement; - - if (state.trackingIdentifiersByEnabledElementUUID[uuid]) { - return state.trackingIdentifiersByEnabledElementUUID[uuid]; - } - - return { trackingUniqueIdentifiers: [] }; -} - -export default { - state, - getters: { - trackingUniqueIdentifiersForElement: getTrackingUniqueIdentifiersForElement, - }, - setters: { - trackingUniqueIdentifiersForElement: setTrackingUniqueIdentifiersForElement, - activeTrackingUniqueIdentifierForElement: setActiveTrackingUniqueIdentifierForElement, - }, -}; diff --git a/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.js b/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.js deleted file mode 100644 index 0edbde32140..00000000000 --- a/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.js +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; - -function OHIFCornerstoneVideoViewport({ - displaySet, -}) { - const { videoUrl } = displaySet; - const mimeType = "video/mp4"; - const [url, setUrl] = useState(null); - useEffect(async () => { - setUrl(await videoUrl); - }); - - // Need to copies of the source to fix a firefox bug - return ( -
- -
- ) -} - -OHIFCornerstoneVideoViewport.propTypes = { - displaySet: PropTypes.object.isRequired, -}; - -export default OHIFCornerstoneVideoViewport; diff --git a/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.tsx b/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.tsx new file mode 100644 index 00000000000..56545b2a07f --- /dev/null +++ b/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; + +function OHIFCornerstoneVideoViewport({ displaySets }) { + if (displaySets && displaySets.length > 1) { + throw new Error( + 'OHIFCornerstoneVideoViewport: only one display set is supported for dicom video right now' + ); + } + + const { videoUrl } = displaySets[0]; + const mimeType = 'video/mp4'; + const [url, setUrl] = useState(null); + + useEffect(() => { + const load = async () => { + await videoUrl; + setUrl(videoUrl); + }; + + load(); + }, [videoUrl]); + + // Need to copies of the source to fix a firefox bug + return ( +
+ +
+ ); +} + +OHIFCornerstoneVideoViewport.propTypes = { + displaySets: PropTypes.arrayOf(PropTypes.object).isRequired, +}; + +export default OHIFCornerstoneVideoViewport; diff --git a/extensions/measurement-tracking/.webpack/webpack.prod.js b/extensions/measurement-tracking/.webpack/webpack.prod.js index 08b25fc133d..e1444944f1e 100644 --- a/extensions/measurement-tracking/.webpack/webpack.prod.js +++ b/extensions/measurement-tracking/.webpack/webpack.prod.js @@ -14,7 +14,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index e070d0141fd..681138fa5cf 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -6,7 +6,7 @@ "license": "MIT", "repository": "OHIF/Viewers", "main": "dist/index.umd.js", - "module": "src/index.js", + "module": "src/index.tsx", "publishConfig": { "access": "public" }, @@ -32,13 +32,13 @@ "peerDependencies": { "@ohif/core": "^3.0.0", "classnames": "^2.2.6", - "cornerstone-core": "^2.6.0", - "cornerstone-tools": "6.0.2", - "dcmjs": "0.16.1", + "@cornerstonejs/core": "^0.13.11", + "@cornerstonejs/tools": "^0.20.15", + "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", + "dcmjs": "^0.24.5", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-cornerstone-viewport": "^4.1.2", "webpack": "^5.50.0", "webpack-merge": "^5.7.3", "@ohif/ui": "^2.0.0" diff --git a/extensions/measurement-tracking/src/_shared/createReportAsync.js b/extensions/measurement-tracking/src/_shared/createReportAsync.tsx similarity index 81% rename from extensions/measurement-tracking/src/_shared/createReportAsync.js rename to extensions/measurement-tracking/src/_shared/createReportAsync.tsx index b6115b57ada..f9641c801f8 100644 --- a/extensions/measurement-tracking/src/_shared/createReportAsync.js +++ b/extensions/measurement-tracking/src/_shared/createReportAsync.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DICOMSR, DicomMetadataStore } from '@ohif/core'; +import { DicomMetadataStore } from '@ohif/core'; /** * @@ -11,6 +11,7 @@ import { DICOMSR, DicomMetadataStore } from '@ohif/core'; */ async function createReportAsync( servicesManager, + commandsManager, dataSource, measurements, options @@ -29,11 +30,15 @@ async function createReportAsync( }); try { - const naturalizedReport = await DICOMSR.storeMeasurements( - measurements, - dataSource, - ['ArrowAnnotate'], - options + const naturalizedReport = await commandsManager.runCommand( + 'storeMeasurements', + { + measurementData: measurements, + dataSource, + additionalFindingTypes: ['ArrowAnnotate'], + options, + }, + 'CORNERSTONE_STRUCTURED_REPORT' ); // The "Mode" route listens for DicomMetadataStore changes diff --git a/extensions/measurement-tracking/src/_shared/createReportDialogPrompt.js b/extensions/measurement-tracking/src/_shared/createReportDialogPrompt.tsx similarity index 100% rename from extensions/measurement-tracking/src/_shared/createReportDialogPrompt.js rename to extensions/measurement-tracking/src/_shared/createReportDialogPrompt.tsx diff --git a/extensions/measurement-tracking/src/_shared/downloadCSVReport.js b/extensions/measurement-tracking/src/_shared/downloadCSVReport.js deleted file mode 100644 index 821d39a3a3d..00000000000 --- a/extensions/measurement-tracking/src/_shared/downloadCSVReport.js +++ /dev/null @@ -1,165 +0,0 @@ -import { DicomMetadataStore } from '@ohif/core'; - -export default function downloadCSVReport( - measurementData, - StudyInstanceUID, - referencedSeriesInstanceUIDs, - MeasurementService -) { - const { VALUE_TYPES } = MeasurementService; - - if (measurementData.length === 0) { - // Prevent download of report with no measurements. - return; - } - - const seriesMetadataMap = {}; - - referencedSeriesInstanceUIDs.forEach(SeriesInstanceUID => { - seriesMetadataMap[SeriesInstanceUID] = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); - }); - - const rows = [ - [ - 'Patient ID', - 'Patient Name', - 'StudyInstanceUID', - 'SeriesInstanceUID', - 'SOPInstanceUID', - 'Label', - 'Long Axis (mm)', - 'Short Axis (mm)', - 'Length (mm)', - 'Area (mmˆ2)', - 'Mean', - 'StDev', - 'Units', - ], - ]; - - measurementData.forEach(measurement => { - switch (measurement.type) { - case VALUE_TYPES.BIDIRECTIONAL: - rows.push(_getBidirectionalRow(measurement, seriesMetadataMap)); - break; - case VALUE_TYPES.ELLIPSE: - rows.push(_getEllipseRow(measurement, seriesMetadataMap)); - break; - case VALUE_TYPES.POINT: - rows.push(_getAdditionalFindingRow(measurement, seriesMetadataMap)); - break; - case VALUE_TYPES.POLYLINE: - rows.push(_getLengthRow(measurement, seriesMetadataMap)); - } - }); - - let csvContent = - 'data:text/csv;charset=utf-8,' + rows.map(row => row.join(',')).join('\n'); - - _createAndDownloadFile(csvContent); -} - -function _getBidirectionalRow(measurement, seriesMetadataMap) { - const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap); - - return [ - ...commonRowItems, - measurement.longestDiameter, // Long Axis (mm) - measurement.shortestDiameter, // Short Axis (mm) - ]; -} - -function _getCommonRowItems(measurement, seriesMetadataMap) { - const seriesMetadata = seriesMetadataMap[measurement.referenceSeriesUID]; - const firstInstance = seriesMetadata.instances[0]; - - const row = [ - firstInstance.PatientID, // Patient ID - firstInstance.PatientName.Alphabetic, // PatientName - measurement.referenceStudyUID, // StudyInstanceUID - measurement.referenceSeriesUID, // SeriesInstanceUID - measurement.SOPInstanceUID, // SOPInstanceUID - measurement.label || '', // Label - ]; - - return row; -} - -function _getAdditionalFindingRow(measurement, seriesMetadataMap) { - const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap); - - return [...commonRowItems]; -} - -function _getEllipseRow(measurement, seriesMetadataMap) { - const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap); - const blankColumns = ['', '', '']; - - const { area, mean, stdDev, unit } = _getEllipseStatsWithUnits( - measurement, - seriesMetadataMap - ); - - return [ - ...commonRowItems, - ...blankColumns, - area, // Area (mmˆ2) - mean, // Mean - stdDev, // StDev - unit, - ]; -} - -function _getEllipseStatsWithUnits(measurement, seriesMetadataMap) { - const seriesMetadata = seriesMetadataMap[measurement.referenceSeriesUID]; - const firstInstance = seriesMetadata.instances[0]; - const modality = firstInstance.Modality; - - switch (modality) { - case 'CT': - return { - mean: `${measurement.mean.toFixed(1)}`, - stdDev: `${measurement.stdDev.toFixed(1)}`, - area: measurement.area.toFixed(1), - unit: 'HU', - }; - case 'PT': - if (measurement.meanSUV) { - return { - mean: `${measurement.meanSUV.toFixed(1)}`, - stdDev: `${measurement.stdDevSUV.toFixed(1)}`, - area: measurement.area.toFixed(1), - unit: 'SUV', - }; - } - // If no SUV calculation then fallback to default. - default: - return { - mean: `${measurement.mean.toFixed(1)}`, - stdDev: `${measurement.stdDev.toFixed(1)}`, - area: measurement.area.toFixed(1), - unit: 'Pixel Values', - }; - } -} - -function _getLengthRow(measurement, seriesMetadataMap) { - const commonRowItems = _getCommonRowItems(measurement, seriesMetadataMap); - const blankColumns = ['', '']; - - return [...commonRowItems, ...blankColumns, measurement.length.toFixed(1)]; -} - -function _createAndDownloadFile(csvContent) { - const encodedUri = encodeURI(csvContent); - - const link = document.createElement('a'); - link.setAttribute('href', encodedUri); - link.setAttribute('download', 'MeasurementReport.csv'); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); -} diff --git a/extensions/measurement-tracking/src/_shared/getTools.js b/extensions/measurement-tracking/src/_shared/getTools.js deleted file mode 100644 index c3d9b9e7740..00000000000 --- a/extensions/measurement-tracking/src/_shared/getTools.js +++ /dev/null @@ -1,36 +0,0 @@ -import csTools from 'cornerstone-tools'; - -const toolsGroupedByType = { - touch: [csTools.PanMultiTouchTool, csTools.ZoomTouchPinchTool], - annotations: [ - csTools.ArrowAnnotateTool, - csTools.BidirectionalTool, - csTools.LengthTool, - csTools.AngleTool, - csTools.FreehandRoiTool, - csTools.EllipticalRoiTool, - csTools.DragProbeTool, - csTools.RectangleRoiTool, - ], - other: [ - csTools.PanTool, - csTools.ZoomTool, - csTools.WwwcTool, - csTools.WwwcRegionTool, - csTools.MagnifyTool, - csTools.StackScrollTool, - csTools.StackScrollMouseWheelTool, - csTools.OverlayTool, - ], -}; - -export default function getTools() { - const tools = []; - Object.keys(toolsGroupedByType).forEach(toolsGroup => - tools.push(...toolsGroupedByType[toolsGroup]) - ); - - return tools; -} - -export { toolsGroupedByType }; diff --git a/extensions/measurement-tracking/src/_shared/setActiveAndPassiveToolsForElement.js b/extensions/measurement-tracking/src/_shared/setActiveAndPassiveToolsForElement.js deleted file mode 100644 index b8348c99080..00000000000 --- a/extensions/measurement-tracking/src/_shared/setActiveAndPassiveToolsForElement.js +++ /dev/null @@ -1,21 +0,0 @@ -import csTools from 'cornerstone-tools'; - -export default function _setActiveAndPassiveToolsForElement(element, tools) { - const BaseAnnotationTool = csTools.importInternal('base/BaseAnnotationTool'); - - tools.forEach(tool => { - if (tool.prototype instanceof BaseAnnotationTool) { - // BaseAnnotationTool would likely come from csTools lib exports - const toolName = new tool().name; - csTools.setToolPassiveForElement(element, toolName); // there may be a better place to determine name; may not be on uninstantiated class - } - }); - - csTools.setToolActiveForElement(element, 'Pan', { mouseButtonMask: 4 }); - csTools.setToolActiveForElement(element, 'Zoom', { mouseButtonMask: 2 }); - csTools.setToolActiveForElement(element, 'Wwwc', { mouseButtonMask: 1 }); - csTools.setToolActiveForElement(element, 'StackScrollMouseWheel', {}); // TODO: Empty options should not be required - csTools.setToolActiveForElement(element, 'PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options - csTools.setToolActiveForElement(element, 'ZoomTouchPinch', {}); - csTools.setToolEnabledForElement(element, 'Overlay', {}); -} diff --git a/extensions/measurement-tracking/src/_shared/setCornerstoneMeasurementActive.js b/extensions/measurement-tracking/src/_shared/setCornerstoneMeasurementActive.js deleted file mode 100644 index b9b689f0835..00000000000 --- a/extensions/measurement-tracking/src/_shared/setCornerstoneMeasurementActive.js +++ /dev/null @@ -1,39 +0,0 @@ -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; - -const { globalImageIdSpecificToolStateManager } = cornerstoneTools; - -export default function setCornerstoneMeasurementActive(measurement) { - const { id } = measurement; - - const toolState = globalImageIdSpecificToolStateManager.saveToolState(); - - Object.keys(toolState).forEach(imageId => { - const imageIdSpecificToolState = toolState[imageId]; - - Object.keys(imageIdSpecificToolState).forEach(toolType => { - const toolSpecificToolState = imageIdSpecificToolState[toolType]; - - const toolSpecificToolData = toolSpecificToolState.data; - - if (toolSpecificToolData && toolSpecificToolData.length) { - toolSpecificToolData.forEach(data => { - data.active = data.id === id ? true : false; - }); - } - }); - }); - - const enabledElements = cornerstoneTools.store.state.enabledElements; - - enabledElements.forEach(element => { - try { - cornerstone.updateImage(element); - } catch (ex) { - // https://github.com/cornerstonejs/cornerstone/blob/master/src/updateImage.js#L16 - // This fails if enabledElement.image is undefined and we have no layers - // Instead of throwing, it should _probably_ do nothing. - // We'll just swallow the exception - } - }); -} diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.jsx b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx similarity index 86% rename from extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.jsx rename to extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx index 5e4e40a993f..7ac6d46f297 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.jsx +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/TrackedMeasurementsContext.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useContext, useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import PropTypes from 'prop-types'; import { Machine } from 'xstate'; import { useMachine } from '@xstate/react'; @@ -18,14 +18,14 @@ TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext'; const useTrackedMeasurements = () => useContext(TrackedMeasurementsContext); const SR_SOPCLASSHANDLERID = - '@ohif/extension-dicom-sr.sopClassHandlerModule.dicom-sr'; + '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr'; /** * * @param {*} param0 */ function TrackedMeasurementsContextProvider( - { servicesManager, extensionManager }, // Bound by consumer + { servicesManager, commandsManager, extensionManager }, // Bound by consumer { children } // Component props ) { const [viewportGrid, viewportGridService] = useViewportGrid(); @@ -34,10 +34,8 @@ function TrackedMeasurementsContextProvider( const machineOptions = Object.assign({}, defaultOptions); machineOptions.actions = Object.assign({}, machineOptions.actions, { jumpToFirstMeasurementInActiveViewport: (ctx, evt) => { - const { - DisplaySetService, - MeasurementService, - } = servicesManager.services; + const { MeasurementService } = servicesManager.services; + const { trackedStudy, trackedSeries } = ctx; const measurements = MeasurementService.getMeasurements(); const trackedMeasurements = measurements.filter( @@ -46,11 +44,11 @@ function TrackedMeasurementsContextProvider( trackedSeries.includes(m.referenceSeriesUID) ); - const id = trackedMeasurements[0].id; + const uid = trackedMeasurements[0].uid; MeasurementService.jumpToMeasurement( viewportGrid.activeViewportIndex, - id + uid ); }, showStructuredReportDisplaySetInActiveViewport: (ctx, evt) => { @@ -58,9 +56,9 @@ function TrackedMeasurementsContextProvider( const StructuredReportDisplaySetInstanceUID = evt.data.createdDisplaySetInstanceUIDs[0].displaySetInstanceUID; - viewportGridService.setDisplaysetForViewport({ + viewportGridService.setDisplaySetsForViewport({ viewportIndex: evt.data.viewportIndex, - displaySetInstanceUID: StructuredReportDisplaySetInstanceUID, + displaySetInstanceUIDs: [StructuredReportDisplaySetInstanceUID], }); } }, @@ -73,16 +71,16 @@ function TrackedMeasurementsContextProvider( const measurementIds = filteredMeasurements.map(fm => fm.id); for (let i = 0; i < measurementIds.length; i++) { - MeasurementService.remove(measurementIds[i], 'app-source'); + MeasurementService.remove(measurementIds[i]); } }, clearAllMeasurements: (ctx, evt) => { const { MeasurementService } = servicesManager.services; const measurements = MeasurementService.getMeasurements(); - const measurementIds = measurements.map(fm => fm.id); + const measurementIds = measurements.map(fm => fm.uid); for (let i = 0; i < measurementIds.length; i++) { - MeasurementService.remove(measurementIds[i], 'app-source'); + MeasurementService.remove(measurementIds[i]); } }, }); @@ -101,6 +99,7 @@ function TrackedMeasurementsContextProvider( }), promptSaveReport: promptSaveReport.bind(null, { servicesManager, + commandsManager, extensionManager, }), promptHydrateStructuredReport: promptHydrateStructuredReport.bind(null, { @@ -134,13 +133,15 @@ function TrackedMeasurementsContextProvider( if (viewports.length > 0) { const activeViewport = viewports[activeViewportIndex]; - if (!activeViewport || !activeViewport.displaySetInstanceUID) { + if (!activeViewport || !activeViewport?.displaySetInstanceUIDs?.length) { return; } + // Todo: Getting the first displaySetInstanceUID is wrong, but we don't have + // tracking fusion viewports yet. This should change when we do. const { DisplaySetService } = servicesManager.services; const displaySet = DisplaySetService.getDisplaySetByUID( - activeViewport.displaySetInstanceUID + activeViewport.displaySetInstanceUIDs[0] ); // If this is an SR produced by our SR SOPClassHandler, @@ -197,6 +198,7 @@ function TrackedMeasurementsContextProvider( TrackedMeasurementsContextProvider.propTypes = { children: PropTypes.oneOf([PropTypes.func, PropTypes.node]), servicesManager: PropTypes.object.isRequired, + commandsManager: PropTypes.object.isRequired, extensionManager: PropTypes.object.isRequired, }; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js index 2baceb80a0a..b7184449a2b 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/_hydrateStructuredReport.js @@ -1,12 +1,15 @@ -import cornerstoneTools from 'cornerstone-tools'; +import { utilities, metaData } from '@cornerstonejs/core'; import OHIF, { DicomMetadataStore } from '@ohif/core'; import getLabelFromDCMJSImportedToolData from './utils/getLabelFromDCMJSImportedToolData'; -import getCornerstoneToolStateToMeasurementSchema from './getCornerstoneToolStateToMeasurementSchema'; import { adapters } from 'dcmjs'; const { guid } = OHIF.utils; -const globalImageIdSpecificToolStateManager = - cornerstoneTools.globalImageIdSpecificToolStateManager; +const { MeasurementReport, CORNERSTONE_3D_TAG } = adapters.Cornerstone3D; + +const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; +const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; + +const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; /** * @@ -24,8 +27,8 @@ export default function _hydrateStructuredReport( // TODO -> We should define a strict versioning somewhere. const mappings = MeasurementService.getSourceMappings( - 'CornerstoneTools', - '4' + CORNERSTONE_3D_TOOLS_SOURCE_NAME, + CORNERSTONE_3D_TOOLS_SOURCE_VERSION ); if (!mappings || !mappings.length) { @@ -40,29 +43,38 @@ export default function _hydrateStructuredReport( displaySet.SOPInstanceUID ); - const { MeasurementReport } = adapters.Cornerstone; - const sopInstanceUIDToImageId = {}; + let imageIdsForToolState = []; displaySet.measurements.forEach(measurement => { const { ReferencedSOPInstanceUID, imageId } = measurement; + imageIdsForToolState.push(imageId); if (!sopInstanceUIDToImageId[ReferencedSOPInstanceUID]) { sopInstanceUIDToImageId[ReferencedSOPInstanceUID] = imageId; } }); + const datasetToUse = _mapLegacyDataSet(instance); + // Use dcmjs to generate toolState. - const storedMeasurementByToolType = MeasurementReport.generateToolState( - instance + const storedMeasurementByAnnotationType = MeasurementReport.generateToolState( + datasetToUse, + // NOTE: we need to pass in the imageIds to dcmjs since the we use them + // for the imageToWorld transformation. The following assumes that the order + // that measurements were added to the display set are the same order as + // the measurementGroups in the instance. + sopInstanceUIDToImageId, + utilities.imageToWorldCoords, + metaData ); // Filter what is found by DICOM SR to measurements we support. - const mappingDefinitions = mappings.map(m => m.definition); + const mappingDefinitions = mappings.map(m => m.annotationType); const hydratableMeasurementsInSR = {}; - Object.keys(storedMeasurementByToolType).forEach(key => { + Object.keys(storedMeasurementByAnnotationType).forEach(key => { if (mappingDefinitions.includes(key)) { - hydratableMeasurementsInSR[key] = storedMeasurementByToolType[key]; + hydratableMeasurementsInSR[key] = storedMeasurementByAnnotationType[key]; } }); @@ -70,12 +82,13 @@ export default function _hydrateStructuredReport( const imageIds = []; // TODO: notification if no hydratable? - Object.keys(hydratableMeasurementsInSR).forEach(toolType => { - const toolDataForToolType = hydratableMeasurementsInSR[toolType]; + Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { + const toolDataForAnnotationType = + hydratableMeasurementsInSR[annotationType]; - toolDataForToolType.forEach(data => { + toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState - const imageId = sopInstanceUIDToImageId[data.sopInstanceUid]; + const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid]; if (!imageIds.includes(imageId)) { imageIds.push(imageId); @@ -88,7 +101,7 @@ export default function _hydrateStructuredReport( for (let i = 0; i < imageIds.length; i++) { const imageId = imageIds[i]; - const { SeriesInstanceUID, StudyInstanceUID } = cornerstone.metaData.get( + const { SeriesInstanceUID, StudyInstanceUID } = metaData.get( 'instance', imageId ); @@ -106,43 +119,49 @@ export default function _hydrateStructuredReport( } } - Object.keys(hydratableMeasurementsInSR).forEach(toolType => { - const toolDataForToolType = hydratableMeasurementsInSR[toolType]; + Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { + const toolDataForAnnotationType = + hydratableMeasurementsInSR[annotationType]; - toolDataForToolType.forEach(data => { + toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState - const imageId = sopInstanceUIDToImageId[data.sopInstanceUid]; + const imageId = sopInstanceUIDToImageId[toolData.sopInstanceUid]; - data.id = guid(); + toolData.uid = guid(); - const instance = cornerstone.metaData.get('instance', imageId); + const instance = metaData.get('instance', imageId); const { - SOPInstanceUID, FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, + // SOPInstanceUID, + // SeriesInstanceUID, + // StudyInstanceUID, } = instance; - // Let the measurement service know we added to toolState - const toMeasurementSchema = getCornerstoneToolStateToMeasurementSchema( - toolType, - MeasurementService, - DisplaySetService, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID + const annotation = { + annotationUID: toolData.annotation.annotationUID, + data: toolData.annotation.data, + metadata: { + toolName: annotationType, + referencedImageId: imageId, + FrameOfReferenceUID, + }, + }; + + const source = MeasurementService.getSource( + CORNERSTONE_3D_TOOLS_SOURCE_NAME, + CORNERSTONE_3D_TOOLS_SOURCE_VERSION ); + annotation.data.label = getLabelFromDCMJSImportedToolData(toolData); - const source = MeasurementService.getSource('CornerstoneTools', '4'); - - data.label = getLabelFromDCMJSImportedToolData(data); + const matchingMapping = mappings.find( + m => m.annotationType === annotationType + ); MeasurementService.addRawMeasurement( source, - toolType, - data, - toMeasurementSchema, + annotationType, + { annotation }, + matchingMapping.toMeasurementSchema, dataSource ); @@ -159,3 +178,66 @@ export default function _hydrateStructuredReport( SeriesInstanceUIDs, }; } + +function _mapLegacyDataSet(dataset) { + const REPORT = 'Imaging Measurements'; + const GROUP = 'Measurement Group'; + const TRACKING_IDENTIFIER = 'Tracking Identifier'; + + // Identify the Imaging Measurements + const imagingMeasurementContent = toArray(dataset.ContentSequence).find( + codeMeaningEquals(REPORT) + ); + + // Retrieve the Measurements themselves + const measurementGroups = toArray( + imagingMeasurementContent.ContentSequence + ).filter(codeMeaningEquals(GROUP)); + + // For each of the supported measurement types, compute the measurement data + const measurementData = {}; + + const cornerstoneToolClasses = + MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; + + const registeredToolClasses = []; + + Object.keys(cornerstoneToolClasses).forEach(key => { + registeredToolClasses.push(cornerstoneToolClasses[key]); + measurementData[key] = []; + }); + + measurementGroups.forEach((measurementGroup, index) => { + const measurementGroupContentSequence = toArray( + measurementGroup.ContentSequence + ); + + const TrackingIdentifierGroup = measurementGroupContentSequence.find( + contentItem => + contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER + ); + + const TrackingIdentifier = TrackingIdentifierGroup.TextValue; + + let [cornerstoneTag, toolName] = TrackingIdentifier.split(':'); + if (supportedLegacyCornerstoneTags.includes(cornerstoneTag)) { + cornerstoneTag = CORNERSTONE_3D_TAG; + } + + const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`; + + TrackingIdentifierGroup.TextValue = mappedTrackingIdentifier; + }); + + return dataset; +} + +const toArray = function(x) { + return Array.isArray(x) ? x : [x]; +}; + +const codeMeaningEquals = codeMeaningName => { + return contentItem => { + return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName; + }; +}; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/getCornerstoneToolStateToMeasurementSchema.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/getCornerstoneToolStateToMeasurementSchema.js deleted file mode 100644 index c548a79292e..00000000000 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/getCornerstoneToolStateToMeasurementSchema.js +++ /dev/null @@ -1,283 +0,0 @@ -export default function getCornerstoneToolStateToMeasurementSchema( - toolType, - MeasurementService, - DisplaySetService, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID -) { - const _getValueTypeFromToolType = toolType => { - const { - POLYLINE, - ELLIPSE, - POINT, - BIDIRECTIONAL, - } = MeasurementService.VALUE_TYPES; - - // TODO -> I get why this was attemped, but its not nearly flexible enough. - // A single measurement may have an ellipse + a bidirectional measurement, for instances. - // You can't define a bidirectional tool as a single type.. - // OHIF-230 - const TOOL_TYPE_TO_VALUE_TYPE = { - Length: POLYLINE, - EllipticalRoi: ELLIPSE, - Bidirectional: BIDIRECTIONAL, - ArrowAnnotate: POINT, - }; - - return TOOL_TYPE_TO_VALUE_TYPE[toolType]; - }; - - switch (toolType) { - case 'Length': - return measurementData => - Length( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType - ); - case 'Bidirectional': - return measurementData => - Bidirectional( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType - ); - case 'EllipticalRoi': - return measurementData => - EllipticalRoi( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType - ); - case 'ArrowAnnotate': - return measurementData => - ArrowAnnotate( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType - ); - } -} - -function Length( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType -) { - const tool = measurementData.toolType || measurementData.toolName; - - const displaySetInstanceUID = _getDisplaySetInstanceUID( - DisplaySetService, - SeriesInstanceUID, - SOPInstanceUID - ); - - const { handles, label } = measurementData; - - const points = []; - Object.keys(handles).map(handle => { - if (['start', 'end'].includes(handle)) { - let point = {}; - if (handles[handle].x) point.x = handles[handle].x; - if (handles[handle].y) point.y = handles[handle].y; - points.push(point); - } - }); - - return { - id: measurementData.id, - SOPInstanceUID: SOPInstanceUID, - FrameOfReferenceUID, - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - displaySetInstanceUID, - description: measurementData.description, - unit: measurementData.unit, - length: measurementData.length, - type: _getValueTypeFromToolType(tool), - points, - label, - }; -} - -function Bidirectional( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType -) { - const tool = measurementData.toolType || measurementData.toolName; - - const displaySetInstanceUID = _getDisplaySetInstanceUID( - DisplaySetService, - SeriesInstanceUID, - SOPInstanceUID - ); - - const { handles, label } = measurementData; - - const longAxis = [handles.start, handles.end]; - const shortAxis = [handles.perpendicularStart, handles.perpendicularEnd]; - - return { - id: measurementData.id, - SOPInstanceUID: SOPInstanceUID, - FrameOfReferenceUID, - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - displaySetInstanceUID, - description: measurementData.description, - unit: measurementData.unit, - shortestDiameter: measurementData.shortestDiameter, - longestDiameter: measurementData.longestDiameter, - type: _getValueTypeFromToolType(tool), - points: { longAxis, shortAxis }, - label, - }; -} - -function EllipticalRoi( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType -) { - const tool = measurementData.toolType || measurementData.toolName; - - const displaySetInstanceUID = _getDisplaySetInstanceUID( - DisplaySetService, - SeriesInstanceUID, - SOPInstanceUID - ); - const { handles, label } = measurementData; - const { start, end } = handles; - - const halfXLength = Math.abs(start.x - end.x) / 2; - const halfYLength = Math.abs(start.y - end.y) / 2; - - const points = []; - const center = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }; - - // To store similar to SR. - if (halfXLength > halfYLength) { - // X-axis major - // Major axis - points.push({ x: center.x - halfXLength, y: center.y }); - points.push({ x: center.x + halfXLength, y: center.y }); - // Minor axis - points.push({ x: center.x, y: center.y - halfYLength }); - points.push({ x: center.x, y: center.y + halfYLength }); - } else { - // Y-axis major - // Major axis - points.push({ x: center.x, y: center.y - halfYLength }); - points.push({ x: center.x, y: center.y + halfYLength }); - // Minor axis - points.push({ x: center.x - halfXLength, y: center.y }); - points.push({ x: center.x + halfXLength, y: center.y }); - } - - return { - id: measurementData.id, - SOPInstanceUID: SOPInstanceUID, - FrameOfReferenceUID, - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - displaySetInstanceUID, - description: measurementData.description, - unit: measurementData.unit, - area: - measurementData.cachedStats && - measurementData.cachedStats - .area /* TODO: Add concept names instead (descriptor) */, - type: _getValueTypeFromToolType(tool), - points, - label, - }; -} - -function ArrowAnnotate( - measurementData, - SOPInstanceUID, - FrameOfReferenceUID, - SeriesInstanceUID, - StudyInstanceUID, - DisplaySetService, - _getValueTypeFromToolType -) { - const tool = measurementData.toolType || measurementData.toolName; - - const displaySetInstanceUID = _getDisplaySetInstanceUID( - DisplaySetService, - SeriesInstanceUID, - SOPInstanceUID - ); - - const { handles } = measurementData; - - const points = []; - Object.keys(handles).map(handle => { - if (['start', 'end'].includes(handle)) { - let point = {}; - if (handles[handle].x) point.x = handles[handle].x; - if (handles[handle].y) point.y = handles[handle].y; - points.push(point); - } - }); - - return { - id: measurementData.id, - SOPInstanceUID: SOPInstanceUID, - FrameOfReferenceUID, - referenceSeriesUID: SeriesInstanceUID, - referenceStudyUID: StudyInstanceUID, - displaySetInstanceUID, - label: measurementData.text, - description: measurementData.description, - unit: measurementData.unit, - text: measurementData.text, - type: _getValueTypeFromToolType(tool), - points, - }; -} - -function _getDisplaySetInstanceUID( - DisplaySetService, - SeriesInstanceUID, - SOPInstanceUID -) { - const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( - SOPInstanceUID, - SeriesInstanceUID - ); - - return displaySet.displaySetInstanceUID; -} diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js index 1385a7f840c..8e3334436f6 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/index.js @@ -2,4 +2,4 @@ export { TrackedMeasurementsContext, TrackedMeasurementsContextProvider, useTrackedMeasurements, -} from './TrackedMeasurementsContext.jsx'; +} from './TrackedMeasurementsContext.tsx'; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js index 45382e8a1a2..a6e45850e6f 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/measurementTrackingMachine.js @@ -44,7 +44,7 @@ const machineConfiguration = { target: 'promptHydrateStructuredReport', cond: 'hasNotIgnoredSRSeriesForHydration', }, - RESTORE_PROMPT_HYDRATE_SR: 'promptHydrateStructuredReport' + RESTORE_PROMPT_HYDRATE_SR: 'promptHydrateStructuredReport', }, }, promptBeginTracking: { @@ -224,7 +224,7 @@ const machineConfiguration = { { target: 'idle', actions: ['ignoreHydrationForSRSeries'], - cond: 'shouldIgnoreHydrationForSR' + cond: 'shouldIgnoreHydrationForSR', }, ], onError: { @@ -308,7 +308,10 @@ const defaultOptions = { ignoredSeries: [...ctx.ignoredSeries, evt.data.SeriesInstanceUID], })), ignoreHydrationForSRSeries: assign((ctx, evt) => ({ - ignoredSRSeriesForHydration: [...ctx.ignoredSRSeriesForHydration, evt.data.srSeriesInstanceUID], + ignoredSRSeriesForHydration: [ + ...ctx.ignoredSRSeriesForHydration, + evt.data.srSeriesInstanceUID, + ], })), addTrackedSeries: assign((ctx, evt) => ({ prevTrackedSeries: [...ctx.trackedSeries], @@ -376,7 +379,7 @@ const defaultOptions = { ctx.trackedSeries.length > 1 || !ctx.trackedSeries.includes(evt.SeriesInstanceUID), hasNotIgnoredSRSeriesForHydration: (ctx, evt) => { - return !ctx.ignoredSRSeriesForHydration.includes(evt.SeriesInstanceUID) + return !ctx.ignoredSRSeriesForHydration.includes(evt.SeriesInstanceUID); }, isNewStudy: (ctx, evt) => !ctx.ignoredSeries.includes(evt.SeriesInstanceUID) && diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js index c44cae8ff71..81419827c2b 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptBeginTracking.js @@ -6,7 +6,7 @@ const RESPONSE = { SET_STUDY_AND_SERIES: 3, }; -function promptUser({ servicesManager, extensionManager }, ctx, evt) { +function promptBeginTracking({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; @@ -33,7 +33,7 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) { id: 'prompt-begin-tracking-cancel', type: 'cancel', text: 'No', - value: RESPONSE.CANCEL + value: RESPONSE.CANCEL, }, { id: 'prompt-begin-tracking-no-do-not-ask-again', @@ -68,4 +68,4 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) { }); } -export default promptUser; +export default promptBeginTracking; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js index f377b375209..089f05faac3 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptHydrateStructuredReport.js @@ -10,10 +10,19 @@ const RESPONSE = { HYDRATE_REPORT: 5, }; -function promptUser({ servicesManager, extensionManager }, ctx, evt) { - const { UIViewportDialogService, DisplaySetService } = servicesManager.services; +function promptHydrateStructuredReport( + { servicesManager, extensionManager }, + ctx, + evt +) { + const { + UIViewportDialogService, + DisplaySetService, + } = servicesManager.services; const { viewportIndex, displaySetInstanceUID } = evt; - const srDisplaySet = DisplaySetService.getDisplaySetByUID(displaySetInstanceUID) + const srDisplaySet = DisplaySetService.getDisplaySetByUID( + displaySetInstanceUID + ); return new Promise(async function(resolve, reject) { const promptResult = await _askTrackMeasurements( @@ -81,4 +90,4 @@ function _askTrackMeasurements(UIViewportDialogService, viewportIndex) { }); } -export default promptUser; +export default promptHydrateStructuredReport; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js index f87d0337538..3e825aae2ca 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptSaveReport.js @@ -1,9 +1,13 @@ -import createReportAsync from './../../_shared/createReportAsync.js'; +import createReportAsync from './../../_shared/createReportAsync'; import createReportDialogPrompt from '../../_shared/createReportDialogPrompt'; import getNextSRSeriesNumber from '../../_shared/getNextSRSeriesNumber'; import RESPONSE from '../../_shared/PROMPT_RESPONSES'; -function promptUser({ servicesManager, extensionManager }, ctx, evt) { +function promptSaveReport( + { servicesManager, commandsManager, extensionManager }, + ctx, + evt +) { const { UIDialogService, MeasurementService, @@ -45,6 +49,7 @@ function promptUser({ servicesManager, extensionManager }, ctx, evt) { displaySetInstanceUIDs = await createReportAsync( servicesManager, + commandsManager, dataSource, trackedMeasurements, { @@ -67,4 +72,4 @@ function promptUser({ servicesManager, extensionManager }, ctx, evt) { }); } -export default promptUser; +export default promptSaveReport; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js index 564efc8adbb..af46628212a 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewSeries.js @@ -7,7 +7,7 @@ const RESPONSE = { NO_NOT_FOR_SERIES: 4, }; -function promptUser({ servicesManager, extensionManager }, ctx, evt) { +function promptTrackNewSeries({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; @@ -105,4 +105,4 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { }); } -export default promptUser; +export default promptTrackNewSeries; diff --git a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js index cd904437c21..9e1456424f0 100644 --- a/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js +++ b/extensions/measurement-tracking/src/contexts/TrackedMeasurementsContext/promptTrackNewStudy.js @@ -7,7 +7,7 @@ const RESPONSE = { NO_NOT_FOR_SERIES: 4, }; -function promptUser({ servicesManager, extensionManager }, ctx, evt) { +function promptTrackNewStudy({ servicesManager, extensionManager }, ctx, evt) { const { UIViewportDialogService } = servicesManager.services; const { viewportIndex, StudyInstanceUID, SeriesInstanceUID } = evt; @@ -104,4 +104,4 @@ function _askSaveDiscardOrCancel(UIViewportDialogService, viewportIndex) { }); } -export default promptUser; +export default promptTrackNewStudy; diff --git a/extensions/measurement-tracking/src/getContextModule.js b/extensions/measurement-tracking/src/getContextModule.tsx similarity index 75% rename from extensions/measurement-tracking/src/getContextModule.js rename to extensions/measurement-tracking/src/getContextModule.tsx index 29acdc402aa..b45cfac5179 100644 --- a/extensions/measurement-tracking/src/getContextModule.js +++ b/extensions/measurement-tracking/src/getContextModule.tsx @@ -4,10 +4,14 @@ import { useTrackedMeasurements, } from './contexts'; -function getContextModule({ servicesManager, extensionManager }) { +function getContextModule({ + servicesManager, + extensionManager, + commandsManager, +}) { const BoundTrackedMeasurementsContextProvider = TrackedMeasurementsContextProvider.bind( null, - { servicesManager, extensionManager } + { servicesManager, extensionManager, commandsManager } ); return [ diff --git a/extensions/measurement-tracking/src/getPanelModule.js b/extensions/measurement-tracking/src/getPanelModule.tsx similarity index 100% rename from extensions/measurement-tracking/src/getPanelModule.js rename to extensions/measurement-tracking/src/getPanelModule.tsx index da65db5c5cb..55ee733b434 100644 --- a/extensions/measurement-tracking/src/getPanelModule.js +++ b/extensions/measurement-tracking/src/getPanelModule.tsx @@ -1,6 +1,6 @@ import { - PanelStudyBrowserTracking, PanelMeasurementTableTracking, + PanelStudyBrowserTracking, } from './panels'; // TODO: diff --git a/extensions/measurement-tracking/src/getViewportModule.js b/extensions/measurement-tracking/src/getViewportModule.tsx similarity index 100% rename from extensions/measurement-tracking/src/getViewportModule.js rename to extensions/measurement-tracking/src/getViewportModule.tsx diff --git a/extensions/measurement-tracking/src/index.js b/extensions/measurement-tracking/src/index.tsx similarity index 76% rename from extensions/measurement-tracking/src/index.js rename to extensions/measurement-tracking/src/index.tsx index 4c1a4dc9be4..b24134ab044 100644 --- a/extensions/measurement-tracking/src/index.js +++ b/extensions/measurement-tracking/src/index.tsx @@ -1,9 +1,9 @@ -import getContextModule from './getContextModule.js'; -import getPanelModule from './getPanelModule.js'; -import getViewportModule from './getViewportModule.js'; +import getContextModule from './getContextModule'; +import getPanelModule from './getPanelModule'; +import getViewportModule from './getViewportModule'; import { id } from './id.js'; -export default { +const measurementTrackingExtension = { /** * Only required property. Should be a unique value across all extensions. */ @@ -34,3 +34,5 @@ export default { }; }, }; + +export default measurementTrackingExtension; diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/ActionButtons.jsx b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/ActionButtons.tsx similarity index 100% rename from extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/ActionButtons.jsx rename to extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/ActionButtons.tsx diff --git a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx similarity index 76% rename from extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js rename to extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx index 924afbcb9e1..2fd99d6ab46 100644 --- a/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.js +++ b/extensions/measurement-tracking/src/panels/PanelMeasurementTableTracking/index.tsx @@ -11,10 +11,8 @@ import { DicomMetadataStore, utils } from '@ohif/core'; import { useDebounce } from '@hooks'; import ActionButtons from './ActionButtons'; import { useTrackedMeasurements } from '../../getContextModule'; -import createReportDialogPrompt from '../../_shared/createReportDialogPrompt'; -import RESPONSES from '../../_shared/PROMPT_RESPONSES'; -import downloadCSVReport from '../../_shared/downloadCSVReport'; +const { downloadCSVReport } = utils; const { formatDate } = utils; const DISPLAY_STUDY_SUMMARY_INITIAL_VALUE = { @@ -146,29 +144,24 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { trackedSeries.includes(m.referenceSeriesUID) ); - downloadCSVReport( - trackedMeasurements, - trackedStudy, - trackedSeries, - MeasurementService - ); + downloadCSVReport(trackedMeasurements, MeasurementService); } - const jumpToImage = ({ id, isActive }) => { - MeasurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, id); + const jumpToImage = ({ uid, isActive }) => { + MeasurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid); - onMeasurementItemClickHandler({ id, isActive }); + onMeasurementItemClickHandler({ uid, isActive }); }; - const onMeasurementItemEditHandler = ({ id, isActive }) => { - const measurement = MeasurementService.getMeasurement(id); - jumpToImage({ id, isActive }); + const onMeasurementItemEditHandler = ({ uid, isActive }) => { + const measurement = MeasurementService.getMeasurement(uid); + jumpToImage({ uid, isActive }); const onSubmitHandler = ({ action, value }) => { switch (action.id) { case 'save': { MeasurementService.update( - id, + uid, { ...measurement, ...value, @@ -225,12 +218,12 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { }); }; - const onMeasurementItemClickHandler = ({ id, isActive }) => { + const onMeasurementItemClickHandler = ({ uid, isActive }) => { if (!isActive) { const measurements = [...displayMeasurements]; - const measurement = measurements.find(m => m.id === id); + const measurement = measurements.find(m => m.uid === uid); - measurements.forEach(m => (m.isActive = m.id !== id ? false : true)); + measurements.forEach(m => (m.isActive = m.uid !== uid ? false : true)); measurement.isActive = true; setDisplayMeasurements(measurements); } @@ -245,8 +238,10 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) { return ( <> -
+
{displayStudySummary.key && ( 2 (S:${seriesNumber}, I:${instanceNumber})`, - ]; - } - case types.POINT: { - const { text } = measurement; // Will display in "short description" - return [`(S:${seriesNumber}, I:${instanceNumber})`]; - } - } -} - -function _round(value, decimals) { - return parseFloat(value).toFixed(decimals); -} - export default PanelMeasurementTableTracking; diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx similarity index 96% rename from extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx rename to extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx index 03f3e84e0d7..655e238b217 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.jsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx @@ -47,14 +47,14 @@ function PanelStudyBrowserTracking({ const [jumpToDisplaySet, setJumpToDisplaySet] = useState(null); const onDoubleClickThumbnailHandler = displaySetInstanceUID => { - viewportGridService.setDisplaysetForViewport({ + viewportGridService.setDisplaySetsForViewport({ viewportIndex: activeViewportIndex, - displaySetInstanceUID, + displaySetInstanceUIDs: [displaySetInstanceUID], }); }; - const activeDisplaySetInstanceUID = - viewports[activeViewportIndex]?.displaySetInstanceUID; + const activeViewportDisplaySetInstanceUIDs = + viewports[activeViewportIndex]?.displaySetInstanceUIDs; const isSingleViewport = numCols === 1 && numRows === 1; @@ -260,8 +260,12 @@ function PanelStudyBrowserTracking({ setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs); if (!shouldCollapseStudy) { - const madeInClient = true - requestDisplaySetCreationForStudy(DisplaySetService, StudyInstanceUID, madeInClient); + const madeInClient = true; + requestDisplaySetCreationForStudy( + DisplaySetService, + StudyInstanceUID, + madeInClient + ); } } @@ -331,7 +335,7 @@ function PanelStudyBrowserTracking({ }} onClickThumbnail={() => {}} onDoubleClickThumbnail={onDoubleClickThumbnailHandler} - activeDisplaySetInstanceUID={activeDisplaySetInstanceUID} + activeDisplaySetInstanceUIDs={activeViewportDisplaySetInstanceUIDs} /> ); } @@ -398,8 +402,12 @@ function _mapDisplaySets( const viewportIdentificator = isSingleViewport ? [] : viewports.reduce((acc, viewportData, index) => { - if (viewportData.displaySetInstanceUID === ds.displaySetInstanceUID) { - acc.push(_viewportLabels[index]); + if ( + viewportData?.displaySetInstanceUIDs?.includes( + ds.displaySetInstanceUID + ) + ) { + acc.push(viewportData.viewportLabel); } return acc; }, []); diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js index baf46c84a31..af0e8a1c909 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getImageSrcFromImageId.js @@ -4,12 +4,10 @@ */ function getImageSrcFromImageId(cornerstone, imageId) { return new Promise((resolve, reject) => { - cornerstone - .loadAndCacheImage(imageId) - .then(image => { - const canvas = document.createElement('canvas'); - cornerstone.renderToCanvas(canvas, image); - + const canvas = document.createElement('canvas'); + cornerstone.utilities + .loadImageToCanvas(canvas, imageId) + .then(imageId => { resolve(canvas.toDataURL()); }) .catch(reject); diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.jsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx similarity index 77% rename from extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.jsx rename to extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx index dbb36556702..bacfefe217a 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.jsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/index.tsx @@ -3,9 +3,17 @@ import PropTypes from 'prop-types'; // import PanelStudyBrowserTracking from './PanelStudyBrowserTracking'; import getImageSrcFromImageId from './getImageSrcFromImageId'; -import getStudiesForPatientByStudyInstanceUID from './getStudiesForPatientByStudyInstanceUID'; import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStudy'; +function _getStudyForPatientUtility(extensionManager) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-default.utilityModule.common' + ); + + const { getStudiesForPatientByStudyInstanceUID } = utilityModule.exports; + return getStudiesForPatientByStudyInstanceUID; +} + /** * Wraps the PanelStudyBrowser and provides features afforded by managers/services * @@ -19,12 +27,16 @@ function WrappedPanelStudyBrowserTracking({ servicesManager, }) { const dataSource = extensionManager.getActiveDataSource()[0]; + + const getStudiesForPatientByStudyInstanceUID = _getStudyForPatientUtility( + extensionManager + ); const _getStudiesForPatientByStudyInstanceUID = getStudiesForPatientByStudyInstanceUID.bind( null, dataSource ); const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn( - commandsManager.getCommand.bind(commandsManager) + extensionManager ); const _requestDisplaySetCreationForStudy = requestDisplaySetCreationForStudy.bind( null, @@ -56,14 +68,13 @@ function WrappedPanelStudyBrowserTracking({ * @returns {func} getImageSrcFromImageId - A utility function powered by * cornerstone */ -function _createGetImageSrcFromImageIdFn(getCommand) { - try { - const command = getCommand('getCornerstoneLibraries', 'VIEWER'); - if (!command) { - return; - } - const { cornerstone } = command.commandFn(); +function _createGetImageSrcFromImageIdFn(extensionManager) { + const utilities = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' + ); + try { + const { cornerstone } = utilities.exports.getCornerstoneLibraries(); return getImageSrcFromImageId.bind(null, cornerstone); } catch (ex) { throw new Error('Required command not found'); diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js deleted file mode 100644 index 08b40e802c6..00000000000 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ /dev/null @@ -1,378 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import PropTypes from 'prop-types'; -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; -import OHIF, { utils } from '@ohif/core'; -import { - Notification, - ViewportActionBar, - useCine, - useViewportGrid, - useViewportDialog, -} from '@ohif/ui'; -import { useTrackedMeasurements } from './../getContextModule'; -import setCornerstoneMeasurementActive from '../_shared/setCornerstoneMeasurementActive'; -import setActiveAndPassiveToolsForElement from '../_shared/setActiveAndPassiveToolsForElement'; -import getTools from '../_shared/getTools'; - -const { formatDate } = utils; - -// TODO -> Get this list from the list of tracked measurements. -// TODO -> We can now get a list of tool names from the measurement service. -// Use the toolnames to check which tools we have instead, using the -// Classes isn't really extensible unless we add the classes to the measurement -// Service definition, which feels wrong. -const { - ArrowAnnotateTool, - BidirectionalTool, - EllipticalRoiTool, - LengthTool, -} = cornerstoneTools; - -const BaseAnnotationTool = cornerstoneTools.importInternal( - 'base/BaseAnnotationTool' -); - -const { StackManager } = OHIF.utils; - -function TrackedCornerstoneViewport(props) { - const { - children, - dataSource, - displaySet, - viewportIndex, - servicesManager, - extensionManager, - commandsManager, - } = props; - - const { ToolBarService } = servicesManager.services; - - const [trackedMeasurements] = useTrackedMeasurements(); - const [ - { activeViewportIndex, viewports }, - viewportGridService, - ] = useViewportGrid(); - const [{ isCineEnabled, cines }, cineService] = useCine(); - const [viewportDialogState, viewportDialogApi] = useViewportDialog(); - const [isTracked, setIsTracked] = useState(false); - const [trackedMeasurementId, setTrackedMeasurementId] = useState(null); - const [element, setElement] = useState(null); - - const onElementEnabled = evt => { - const eventData = evt.detail; - const targetElement = eventData.element; - const tools = getTools(); - const toolAlias = ToolBarService.state.primaryToolId; - - // Activate appropriate tool bindings for element - setActiveAndPassiveToolsForElement(targetElement, tools); - cornerstoneTools.setToolActiveForElement(targetElement, toolAlias, { - mouseButtonMask: 1, - }); - - // Set dashed, based on tracking, for this viewport - const allTools = cornerstoneTools.store.state.tools; - const toolsForElement = allTools.filter( - tool => tool.element === targetElement - ); - - toolsForElement.forEach(tool => { - if ( - tool instanceof ArrowAnnotateTool || - tool instanceof BidirectionalTool || - tool instanceof EllipticalRoiTool || - tool instanceof LengthTool - ) { - const configuration = tool.configuration; - - configuration.renderDashed = !isTracked; - - tool.configuration = configuration; - } else if (tool instanceof BaseAnnotationTool) { - const configuration = tool.configuration; - - configuration.renderDashed = true; - - tool.configuration = configuration; - } - }); - - // Update image after setting tool config - const enabledElement = cornerstone.getEnabledElement(targetElement); - - if (enabledElement.image) { - cornerstone.updateImage(targetElement); - } - - setElement(targetElement); - - const OHIFCornerstoneEnabledElementEvent = new CustomEvent( - 'ohif-cornerstone-enabled-element-event', - { - detail: { - context: 'ACTIVE_VIEWPORT::TRACKED', - enabledElement: targetElement, - viewportIndex, - }, - } - ); - - document.dispatchEvent(OHIFCornerstoneEnabledElementEvent); - }; - - useEffect(() => { - if (!element) { - return; - } - const allTools = cornerstoneTools.store.state.tools; - const toolsForElement = allTools.filter(tool => tool.element === element); - - toolsForElement.forEach(tool => { - if ( - tool instanceof ArrowAnnotateTool || - tool instanceof BidirectionalTool || - tool instanceof EllipticalRoiTool || - tool instanceof LengthTool - ) { - const configuration = tool.configuration; - - configuration.renderDashed = !isTracked; - - tool.configuration = configuration; - } - }); - - const enabledElement = cornerstone.getEnabledElement(element); - - if (enabledElement.image) { - cornerstone.updateImage(element); - } - }, [isTracked]); - - // We have... - // StudyInstanceUid, DisplaySetInstanceUid - // Use displaySetInstanceUid --> SeriesInstanceUid - // Get meta for series, map to actionBar - // const displaySet = DisplaySetService.getDisplaySetByUID( - // dSet.displaySetInstanceUID - // ); - // TODO: This display contains the meta for all instances. - // That can't be right... - // console.log('DISPLAYSET', displaySet); - // const seriesMeta = DicomMetadataStore.getSeries(this.props.displaySet.StudyInstanceUID, ''); - // console.log(seriesMeta); - - // TODO: Share this logic so it isn't out of sync where we retrieve - const firstViewportIndexWithMatchingDisplaySetUid = viewports.findIndex( - vp => vp.displaySetInstanceUID === displaySet.displaySetInstanceUID - ); - const { trackedSeries } = trackedMeasurements.context; - - const { - Modality, - SeriesDate, - SeriesDescription, - SeriesInstanceUID, - SeriesNumber, - } = displaySet; - - const { - PatientID, - PatientName, - PatientSex, - PatientAge, - SliceThickness, - SpacingBetweenSlices, - ManufacturerModelName, - } = displaySet.images[0]; - - if (trackedSeries.includes(SeriesInstanceUID) !== isTracked) { - setIsTracked(!isTracked); - } - - const label = viewports.length > 1 ? _viewportLabels[viewportIndex] : ''; - - function switchMeasurement(direction) { - if (!element) { - // Element not yet enabled. - return; - } - - const newTrackedMeasurementId = _getNextMeasurementId( - direction, - servicesManager, - trackedMeasurementId, - trackedMeasurements - ); - - if (!newTrackedMeasurementId) { - return; - } - - setTrackedMeasurementId(newTrackedMeasurementId); - - const { MeasurementService } = servicesManager.services; - const measurements = MeasurementService.getMeasurements(); - const measurement = measurements.find( - m => m.id === newTrackedMeasurementId - ); - - setCornerstoneMeasurementActive(measurement); - - MeasurementService.jumpToMeasurement( - viewportIndex, - newTrackedMeasurementId - ); - } - - const renderViewport = () => { - const { component: Component } = extensionManager.getModuleEntry( - '@ohif/extension-cornerstone.viewportModule.cornerstone' - ); - return ( - - ); - }; - - const cine = cines[viewportIndex]; - const isPlaying = (cine && cine.isPlaying) || false; - const frameRate = (cine && cine.frameRate) || 24; - - return ( - <> - { - evt.stopPropagation(); - evt.preventDefault(); - }} - onSeriesChange={direction => switchMeasurement(direction)} - studyData={{ - label, - isTracked, - isLocked: false, - isRehydratable: false, - studyDate: formatDate(SeriesDate), // TODO: This is series date. Is that ok? - currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based - seriesDescription: SeriesDescription, - modality: Modality, - patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', - patientSex: PatientSex || '', - patientAge: PatientAge || '', - MRN: PatientID || '', - thickness: SliceThickness - ? `${parseFloat(SliceThickness).toFixed(2)}mm` - : '', - spacing: - SpacingBetweenSlices !== undefined - ? `${parseFloat(SpacingBetweenSlices).toFixed(2)}mm` - : '', - scanner: ManufacturerModelName || '', - }, - }} - showNavArrows={!isCineEnabled} - showCine={isCineEnabled} - cineProps={{ - isPlaying, - onClose: () => commandsManager.runCommand('toggleCine'), - onPlayPauseChange: isPlaying => - cineService.setCine({ id: activeViewportIndex, isPlaying }), - onFrameRateChange: frameRate => - cineService.setCine({ id: activeViewportIndex, frameRate }), - }} - /> - {/* TODO: Viewport interface to accept stack or layers of content like this? */} -
- {renderViewport()} -
- {viewportDialogState.viewportIndex === viewportIndex && ( - - )} -
-
- - ); -} - -TrackedCornerstoneViewport.propTypes = { - displaySet: PropTypes.object.isRequired, - viewportIndex: PropTypes.number.isRequired, - dataSource: PropTypes.object, - children: PropTypes.node, - customProps: PropTypes.object, -}; - -TrackedCornerstoneViewport.defaultProps = { - customProps: {}, -}; - -const _viewportLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']; - -function _getNextMeasurementId( - direction, - servicesManager, - trackedMeasurementId, - trackedMeasurements -) { - const { MeasurementService } = servicesManager.services; - const measurements = MeasurementService.getMeasurements(); - - const { trackedSeries } = trackedMeasurements.context; - - // Get the potentially trackable measurements for this series, - // The measurements to jump between are the same - // regardless if this series is tracked or not. - - const filteredMeasurements = measurements.filter(m => - trackedSeries.includes(m.referenceSeriesUID) - ); - - if (!filteredMeasurements.length) { - // No measurements on this series. - return; - } - - const measurementCount = filteredMeasurements.length; - - const ids = filteredMeasurements.map(fm => fm.id); - let measurementIndex = ids.findIndex(id => id === trackedMeasurementId); - - if (measurementIndex === -1) { - // Not tracking a measurement, or previous measurement now deleted, revert to 0. - measurementIndex = 0; - } else { - if (direction === 'left') { - measurementIndex--; - - if (measurementIndex < 0) { - measurementIndex = measurementCount - 1; - } - } else if (direction === 'right') { - measurementIndex++; - - if (measurementIndex === measurementCount) { - measurementIndex = 0; - } - } - } - - const newTrackedMeasurementId = ids[measurementIndex]; - - return newTrackedMeasurementId; -} - -export default TrackedCornerstoneViewport; diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx new file mode 100644 index 00000000000..2f00602d283 --- /dev/null +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx @@ -0,0 +1,296 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import OHIF, { utils } from '@ohif/core'; + +import { + Notification, + ViewportActionBar, + useCine, + useViewportGrid, + useViewportDialog, +} from '@ohif/ui'; + +import { annotation } from '@cornerstonejs/tools'; +import { useTrackedMeasurements } from './../getContextModule'; + +const { formatDate } = utils; + +function TrackedCornerstoneViewport(props) { + const { + children, + displaySets, + viewportIndex, + viewportLabel, + servicesManager, + extensionManager, + commandsManager, + } = props; + + const { + MeasurementService, + CornerstoneViewportService, + } = servicesManager.services; + + // Todo: handling more than one displaySet on the same viewport + const displaySet = displaySets[0]; + + const [trackedMeasurements] = useTrackedMeasurements(); + const [{ activeViewportIndex }] = useViewportGrid(); + const [{ isCineEnabled, cines }, cineService] = useCine(); + const [viewportDialogState] = useViewportDialog(); + const [isTracked, setIsTracked] = useState(false); + const [trackedMeasurementUID, setTrackedMeasurementUID] = useState(null); + const [element, setElement] = useState(null); + + const { trackedSeries } = trackedMeasurements.context; + + const viewportId = CornerstoneViewportService.getViewportId(viewportIndex); + + const { + Modality, + SeriesDate, + SeriesDescription, + SeriesInstanceUID, + SeriesNumber, + } = displaySet; + + const { + PatientID, + PatientName, + PatientSex, + PatientAge, + SliceThickness, + SpacingBetweenSlices, + ManufacturerModelName, + } = displaySet.images[0]; + + useEffect(() => { + if (isTracked) { + annotation.config.style.setViewportToolStyles(viewportId, { + global: { + lineDash: '', + }, + }); + + CornerstoneViewportService.getRenderingEngine().renderViewport( + viewportId + ); + + return; + } + + annotation.config.style.setViewportToolStyles(`viewport-${viewportIndex}`, { + global: { + lineDash: '4,4', + }, + }); + + CornerstoneViewportService.getRenderingEngine().renderViewport(viewportId); + + return () => { + annotation.config.style.setViewportToolStyles(viewportId, {}); + }; + }, [isTracked]); + + useEffect(() => { + if (!cines || !cines[viewportIndex]) { + return; + } + + const cine = cines[viewportIndex]; + const isPlaying = (cine && cine.isPlaying) || false; + const frameRate = (cine && cine.frameRate) || 24; + + const validFrameRate = Math.max(frameRate, 1); + + if (isPlaying) { + cineService.playClip(element, { + framesPerSecond: validFrameRate, + }); + } else { + cineService.stopClip(element); + } + }, [cines, viewportIndex, cineService, element, displaySet]); + + if (trackedSeries.includes(SeriesInstanceUID) !== isTracked) { + setIsTracked(!isTracked); + } + + /** + * OnElementEnabled callback which is called after the cornerstoneExtension + * has enabled the element. Note: we delegate all the image rendering to + * cornerstoneExtension, so we don't need to do anything here regarding + * the image rendering, element enabling etc. + */ + const onElementEnabled = evt => { + setElement(evt.detail.element); + }; + + function switchMeasurement(direction) { + const newTrackedMeasurementUID = _getNextMeasurementUID( + direction, + servicesManager, + trackedMeasurementUID, + trackedMeasurements + ); + + if (!newTrackedMeasurementUID) { + return; + } + + setTrackedMeasurementUID(newTrackedMeasurementUID); + + MeasurementService.jumpToMeasurement( + viewportIndex, + newTrackedMeasurementUID + ); + } + + const getCornerstoneViewport = () => { + const { component: Component } = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.viewportModule.cornerstone' + ); + + return ; + }; + + const cine = cines[viewportIndex]; + const isPlaying = (cine && cine.isPlaying) || false; + + return ( + <> + { + evt.stopPropagation(); + evt.preventDefault(); + }} + onSeriesChange={direction => switchMeasurement(direction)} + studyData={{ + label: viewportLabel, + isTracked, + isLocked: false, + isRehydratable: false, + studyDate: formatDate(SeriesDate), // TODO: This is series date. Is that ok? + currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based + seriesDescription: SeriesDescription, + modality: Modality, + patientInformation: { + patientName: PatientName + ? OHIF.utils.formatPN(PatientName.Alphabetic) + : '', + patientSex: PatientSex || '', + patientAge: PatientAge || '', + MRN: PatientID || '', + thickness: SliceThickness + ? `${parseFloat(SliceThickness).toFixed(2)}mm` + : '', + spacing: + SpacingBetweenSlices !== undefined + ? `${parseFloat(SpacingBetweenSlices).toFixed(2)}mm` + : '', + scanner: ManufacturerModelName || '', + }, + }} + showNavArrows={!isCineEnabled} + showCine={isCineEnabled} + cineProps={{ + isPlaying, + onClose: () => commandsManager.runCommand('toggleCine'), + onPlayPauseChange: isPlaying => + cineService.setCine({ + id: activeViewportIndex, + isPlaying, + }), + onFrameRateChange: frameRate => + cineService.setCine({ + id: activeViewportIndex, + frameRate, + }), + }} + /> + {/* TODO: Viewport interface to accept stack or layers of content like this? */} +
+ {getCornerstoneViewport()} +
+ {viewportDialogState.viewportIndex === viewportIndex && ( + + )} +
+
+ + ); +} + +TrackedCornerstoneViewport.propTypes = { + displaySets: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired, + viewportIndex: PropTypes.number.isRequired, + dataSource: PropTypes.object, + children: PropTypes.node, + customProps: PropTypes.object, +}; + +TrackedCornerstoneViewport.defaultProps = { + customProps: {}, +}; + +function _getNextMeasurementUID( + direction, + servicesManager, + trackedMeasurementId, + trackedMeasurements +) { + const { MeasurementService } = servicesManager.services; + const measurements = MeasurementService.getMeasurements(); + + const { trackedSeries } = trackedMeasurements.context; + + // Get the potentially trackable measurements for this series, + // The measurements to jump between are the same + // regardless if this series is tracked or not. + + const filteredMeasurements = measurements.filter(m => + trackedSeries.includes(m.referenceSeriesUID) + ); + + if (!filteredMeasurements.length) { + // No measurements on this series. + return; + } + + const measurementCount = filteredMeasurements.length; + + const uids = filteredMeasurements.map(fm => fm.uid); + let measurementIndex = uids.findIndex(uid => uid === trackedMeasurementId); + + if (measurementIndex === -1) { + // Not tracking a measurement, or previous measurement now deleted, revert to 0. + measurementIndex = 0; + } else { + if (direction === 'left') { + measurementIndex--; + + if (measurementIndex < 0) { + measurementIndex = measurementCount - 1; + } + } else if (direction === 'right') { + measurementIndex++; + + if (measurementIndex === measurementCount) { + measurementIndex = 0; + } + } + } + + const newTrackedMeasurementId = uids[measurementIndex]; + + return newTrackedMeasurementId; +} + +export default TrackedCornerstoneViewport; diff --git a/extensions/measurement-tracking/src/viewports/ViewportLoadingIndicator.js b/extensions/measurement-tracking/src/viewports/ViewportLoadingIndicator.js deleted file mode 100644 index 2030d0d238c..00000000000 --- a/extensions/measurement-tracking/src/viewports/ViewportLoadingIndicator.js +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -import PropTypes from 'prop-types'; - -const ViewportLoadingIndicator = ({ error }) => { - if (error) { - return ( - <> -
-
-

Error Loading Image

-

An error has occurred.

-

{error.message}

-
- - ); - } - - return ( - <> -
-
-

Loading...

-
- - ); -}; - -ViewportLoadingIndicator.propTypes = { - percentComplete: PropTypes.number, - error: PropTypes.object, -}; - -ViewportLoadingIndicator.defaultProps = { - percentComplete: 0, - error: null, -}; - -export default ViewportLoadingIndicator; diff --git a/extensions/measurement-tracking/src/viewports/ViewportOverlay.js b/extensions/measurement-tracking/src/viewports/ViewportOverlay.js deleted file mode 100644 index b3f7e31dea7..00000000000 --- a/extensions/measurement-tracking/src/viewports/ViewportOverlay.js +++ /dev/null @@ -1,95 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import cornerstone from 'cornerstone-core'; -import classnames from 'classnames'; - -const ViewportOverlay = ({ - imageId, - scale, - windowWidth, - windowCenter, - imageIndex, - stackSize, - activeTools, -}) => { - const topLeft = 'top-viewport left-viewport'; - const topRight = 'top-viewport right-viewport-scrollbar'; - const bottomRight = 'bottom-viewport right-viewport-scrollbar'; - const bottomLeft = 'bottom-viewport left-viewport'; - const overlay = 'absolute pointer-events-none'; - - const isZoomActive = activeTools.includes('Zoom'); - const isWwwcActive = activeTools.includes('Wwwc'); - - if (!imageId) { - return null; - } - - // TODO: this component should be presentational only. Right now it has a weird dependency on Cornerstone - const generalImageModule = - cornerstone.metaData.get('generalImageModule', imageId) || {}; - const { instanceNumber } = generalImageModule; - - return ( -
-
- {isZoomActive && ( -
- Zoom: - {scale.toFixed(2)}x -
- )} - {isWwwcActive && ( -
- W: - - {windowWidth.toFixed(0)} - - L: - {windowCenter.toFixed(0)} -
- )} -
-
- {stackSize > 1 && ( -
- I: - - {`${instanceNumber} (${imageIndex}/${stackSize})`} - -
- )} -
-
-
-
- ); -}; - -ViewportOverlay.propTypes = { - scale: PropTypes.number.isRequired, - windowWidth: PropTypes.number.isRequired, - windowCenter: PropTypes.number.isRequired, - imageId: PropTypes.string.isRequired, - imageIndex: PropTypes.number.isRequired, - stackSize: PropTypes.number.isRequired, - activeTools: PropTypes.arrayOf(PropTypes.string), -}; - -ViewportOverlay.defaultProps = { - activeTools: [], -}; - -export default ViewportOverlay; diff --git a/extensions/tmtv/.webpack/webpack.dev.js b/extensions/tmtv/.webpack/webpack.dev.js new file mode 100644 index 00000000000..1ae30844802 --- /dev/null +++ b/extensions/tmtv/.webpack/webpack.dev.js @@ -0,0 +1,8 @@ +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + return webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); +}; diff --git a/extensions/tmtv/.webpack/webpack.prod.js b/extensions/tmtv/.webpack/webpack.prod.js new file mode 100644 index 00000000000..946363c5366 --- /dev/null +++ b/extensions/tmtv/.webpack/webpack.prod.js @@ -0,0 +1,43 @@ +const webpack = require('webpack'); +const merge = require('webpack-merge'); +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const pkg = require('./../package.json'); + +const ROOT_DIR = path.join(__dirname, './..'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); + + return merge(commonConfig, { + stats: { + colors: true, + hash: true, + timings: true, + assets: true, + chunks: false, + chunkModules: false, + modules: false, + children: false, + warnings: true, + }, + optimization: { + minimize: true, + sideEffects: true, + }, + output: { + path: ROOT_DIR, + library: 'OHIFExtDICOMSR', + libraryTarget: 'umd', + libraryExport: 'default', + filename: pkg.main, + }, + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + ], + }); +}; diff --git a/extensions/tmtv/LICENSE b/extensions/tmtv/LICENSE new file mode 100644 index 00000000000..19e20dd35ca --- /dev/null +++ b/extensions/tmtv/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Open Health Imaging Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/tmtv/README.md b/extensions/tmtv/README.md new file mode 100644 index 00000000000..f1753525b11 --- /dev/null +++ b/extensions/tmtv/README.md @@ -0,0 +1 @@ +# TMTV Extension diff --git a/extensions/tmtv/babel.config.js b/extensions/tmtv/babel.config.js new file mode 100644 index 00000000000..325ca2a8ee7 --- /dev/null +++ b/extensions/tmtv/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/extensions/tmtv/package.json b/extensions/tmtv/package.json new file mode 100644 index 00000000000..85bf6c78078 --- /dev/null +++ b/extensions/tmtv/package.json @@ -0,0 +1,43 @@ +{ + "name": "@ohif/extension-tmtv", + "version": "3.0.1", + "description": "OHIF extension for Total Metabolic Tumore Volume", + "author": "OHIF", + "license": "MIT", + "repository": "OHIF/Viewers", + "main": "dist/index.umd.js", + "module": "src/index.tsx", + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1.16.0" + }, + "files": [ + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "^3.0.0", + "@ohif/ui": "^2.0.0", + "dcmjs": "0.22.0", + "dicom-parser": "^1.8.9", + "hammerjs": "^2.0.8", + "prop-types": "^15.6.2", + "react": "^17.0.2" + }, + "dependencies": { + "@babel/runtime": "7.7.6", + "classnames": "^2.2.6" + } +} diff --git a/extensions/tmtv/src/Panels/PanelPetSUV.tsx b/extensions/tmtv/src/Panels/PanelPetSUV.tsx new file mode 100644 index 00000000000..36483c010ec --- /dev/null +++ b/extensions/tmtv/src/Panels/PanelPetSUV.tsx @@ -0,0 +1,243 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { Input, Button } from '@ohif/ui'; +import { classes, DicomMetadataStore } from '@ohif/core'; +import { useTranslation } from 'react-i18next'; + +const DEFAULT_MEATADATA = { + PatientWeight: null, + PatientSex: null, + SeriesTime: null, + RadiopharmaceuticalInformationSequence: { + RadionuclideTotalDose: null, + RadionuclideHalfLife: null, + RadiopharmaceuticalStartTime: null, + }, +}; + +/* + * PETSUV panel enables the user to modify the patient related information, such as + * patient sex, patientWeight. This is allowed since + * sometimes these metadata are missing or wrong. By changing them + * @param param0 + * @returns + */ +export default function PanelPetSUV({ servicesManager, commandsManager }) { + const { t } = useTranslation('PanelSUV'); + const { DisplaySetService } = servicesManager.services; + const [metadata, setMetadata] = useState(DEFAULT_MEATADATA); + const [ptDisplaySet, setPtDisplaySet] = useState(null); + + const handleMetadataChange = useCallback( + metadata => { + setMetadata(prevState => { + const newState = { ...prevState }; + Object.keys(metadata).forEach(key => { + if (typeof metadata[key] === 'object') { + newState[key] = { + ...prevState[key], + ...metadata[key], + }; + } else { + newState[key] = metadata[key]; + } + }); + return newState; + }); + }, + [metadata] + ); + + const getMatchingPTDisplaySet = useCallback(() => { + const ptDisplaySet = commandsManager.runCommand('getMatchingPTDisplaySet'); + + if (!ptDisplaySet) { + return; + } + + const metadata = commandsManager.runCommand('getPTMetadata', { + ptDisplaySet, + }); + + return { + ptDisplaySet, + metadata, + }; + }, []); + + useEffect(() => { + const displaySets = DisplaySetService.activeDisplaySets; + + if (!displaySets.length) { + return; + } + + const displaySetInfo = getMatchingPTDisplaySet(); + + if (!displaySetInfo) { + return; + } + + const { ptDisplaySet, metadata } = displaySetInfo; + setPtDisplaySet(ptDisplaySet); + setMetadata(metadata); + }, []); + + // get the patientMetadata from the StudyInstanceUIDs and update the state + useEffect(() => { + const { unsubscribe } = DisplaySetService.subscribe( + DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, + () => { + const displaySetInfo = getMatchingPTDisplaySet(); + + if (!displaySetInfo) { + return; + } + + const { ptDisplaySet, metadata } = displaySetInfo; + setPtDisplaySet(ptDisplaySet); + setMetadata(metadata); + } + ); + return () => { + unsubscribe(); + }; + }, []); + + function updateMetadata() { + if (!ptDisplaySet) { + throw new Error('No ptDisplaySet found'); + } + // metadata should be dcmjs naturalized + DicomMetadataStore.updateMetadataForSeries( + ptDisplaySet.StudyInstanceUID, + ptDisplaySet.SeriesInstanceUID, + metadata + ); + + // update the displaySets + DisplaySetService.setDisplaySetMetadataInvalidated( + ptDisplaySet.displaySetInstanceUID + ); + } + + return ( +
+ { +
+
+ { + handleMetadataChange({ + PatientSex: e.target.value, + }); + }} + /> + { + handleMetadataChange({ + PatientWeight: e.target.value, + }); + }} + /> + { + handleMetadataChange({ + RadiopharmaceuticalInformationSequence: { + RadionuclideTotalDose: e.target.value, + }, + }); + }} + /> + { + handleMetadataChange({ + RadiopharmaceuticalInformationSequence: { + RadionuclideHalfLife: e.target.value, + }, + }); + }} + /> + { + handleMetadataChange({ + RadiopharmaceuticalInformationSequence: { + RadiopharmaceuticalStartTime: e.target.value, + }, + }); + }} + /> + {}} + /> +
+ +
+ } +
+ ); +} + +PanelPetSUV.propTypes = { + servicesManager: PropTypes.shape({ + services: PropTypes.shape({ + MeasurementService: PropTypes.shape({ + getMeasurements: PropTypes.func.isRequired, + subscribe: PropTypes.func.isRequired, + EVENTS: PropTypes.object.isRequired, + VALUE_TYPES: PropTypes.object.isRequired, + }).isRequired, + }).isRequired, + }).isRequired, +}; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx new file mode 100644 index 00000000000..415b80015e4 --- /dev/null +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Button, ButtonGroup } from '@ohif/ui'; +import { useTranslation } from 'react-i18next'; + +function ExportReports({ segmentations, tmtvValue, config, commandsManager }) { + const { t } = useTranslation('PanelSUVExport'); + + return ( + <> + {segmentations?.length ? ( +
+ + + + + + +
+ ) : null} + + ); +} + +export default ExportReports; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx new file mode 100644 index 00000000000..83f11cbc36d --- /dev/null +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx @@ -0,0 +1,322 @@ +import React, { useEffect, useState, useCallback, useReducer } from 'react'; +import PropTypes from 'prop-types'; +import { SegmentationTable, Button, Icon } from '@ohif/ui'; +import classnames from 'classnames'; + +import { useTranslation } from 'react-i18next'; +import segmentationEditHandler from './segmentationEditHandler'; +import ExportReports from './ExportReports'; +import ROIThresholdConfiguration, { + ROI_STAT, +} from './ROIThresholdConfiguration'; + +const LOWER_THRESHOLD_DEFAULT = 0; +const UPPER_THRESHOLD_DEFAULT = 100; +const WEIGHT_DEFAULT = 0.41; // a default weight for suv max often used in the literature +const DEFAULT_STRATEGY = ROI_STAT; + +function reducer(state, action) { + const { payload } = action; + const { strategy, lower, upper, weight } = payload; + + switch (action.type) { + case 'setStrategy': + return { + ...state, + strategy, + }; + case 'setThreshold': + return { + ...state, + lower: lower ? lower : state.lower, + upper: upper ? upper : state.upper, + }; + case 'setWeight': + return { + ...state, + weight, + }; + default: + return state; + } +} + +export default function PanelRoiThresholdSegmentation({ + servicesManager, + commandsManager, +}) { + const { SegmentationService } = servicesManager.services; + + const { t } = useTranslation('PanelSUV'); + const [showConfig, setShowConfig] = useState(false); + const [labelmapLoading, setLabelmapLoading] = useState(false); + const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); + const [segmentations, setSegmentations] = useState(() => + SegmentationService.getSegmentations() + ); + + const [config, dispatch] = useReducer(reducer, { + strategy: DEFAULT_STRATEGY, + lower: LOWER_THRESHOLD_DEFAULT, + upper: UPPER_THRESHOLD_DEFAULT, + weight: WEIGHT_DEFAULT, + }); + + const [tmtvValue, setTmtvValue] = useState(null); + + const runCommand = useCallback( + (commandName, commandOptions = {}) => { + return commandsManager.runCommand(commandName, commandOptions); + }, + [commandsManager] + ); + + const handleTMTVCalculation = useCallback(() => { + const tmtv = runCommand('calculateTMTV', { segmentations }); + + if (tmtv !== undefined) { + setTmtvValue(tmtv.toFixed(2)); + } + }, [segmentations, runCommand]); + + const handleROIThresholding = useCallback(() => { + const labelmap = runCommand('thresholdSegmentationByRectangleROITool', { + segmentationId: selectedSegmentationId, + config, + }); + + const lesionStats = runCommand('getLesionStats', { labelmap }); + const suvPeak = runCommand('calculateSuvPeak', { labelmap }); + const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue; + + // update segDetails with the suv peak for the active segmentation + const segmentation = SegmentationService.getSegmentation( + selectedSegmentationId + ); + + const data = { + lesionStats, + suvPeak, + lesionGlyoclysisStats, + }; + + const notYetUpdatedAtSource = true; + SegmentationService.addOrUpdateSegmentation( + selectedSegmentationId, + { + ...segmentation, + data: Object.assign(segmentation.data, data), + text: [`SUV Peak: ${suvPeak.suvPeak.toFixed(2)}`], + }, + notYetUpdatedAtSource + ); + + handleTMTVCalculation(); + }, [selectedSegmentationId, config]); + + /** + * Update UI based on segmentation changes (added, removed, updated) + */ + useEffect(() => { + // ~~ Subscription + const added = SegmentationService.EVENTS.SEGMENTATION_ADDED; + const updated = SegmentationService.EVENTS.SEGMENTATION_UPDATED; + const subscriptions = []; + + [added, updated].forEach(evt => { + const { unsubscribe } = SegmentationService.subscribe(evt, () => { + const segmentations = SegmentationService.getSegmentations(); + setSegmentations(segmentations); + }); + subscriptions.push(unsubscribe); + }); + + return () => { + subscriptions.forEach(unsub => { + unsub(); + }); + }; + }, []); + + useEffect(() => { + const { unsubscribe } = SegmentationService.subscribe( + SegmentationService.EVENTS.SEGMENTATION_REMOVED, + () => { + const segmentations = SegmentationService.getSegmentations(); + setSegmentations(segmentations); + + if (segmentations.length > 0) { + setSelectedSegmentationId(segmentations[0].id); + handleTMTVCalculation(); + } else { + setSelectedSegmentationId(null); + setTmtvValue(null); + } + } + ); + + return () => { + unsubscribe(); + }; + }, []); + + /** + * Toggle visibility of the segmentation + */ + useEffect(() => { + const subscription = SegmentationService.subscribe( + SegmentationService.EVENTS.SEGMENTATION_VISIBILITY_CHANGED, + ({ segmentation }) => { + runCommand('toggleSegmentationVisibility', { + segmentationId: segmentation.id, + }); + } + ); + return () => { + subscription.unsubscribe(); + }; + }, [SegmentationService]); + + /** + * Whenever the segmentations change, update the TMTV calculations + */ + useEffect(() => { + if (!selectedSegmentationId && segmentations.length > 0) { + setSelectedSegmentationId(segmentations[0].id); + } + + handleTMTVCalculation(); + }, [segmentations, selectedSegmentationId]); + + return ( +
+
+
+ + +
+
{ + setShowConfig(!showConfig); + }} + > +
+ {t('ROI Threshold Configuration')} +
+
+ {showConfig && ( + + )} + {/* show segmentation table */} +
+ {segmentations?.length ? ( + { + runCommand('setSegmentationActiveForToolGroups', { + segmentationId: id, + }); + setSelectedSegmentationId(id); + }} + onToggleVisibility={id => { + SegmentationService.toggleSegmentationsVisibility([id]); + }} + onToggleVisibilityAll={ids => { + SegmentationService.toggleSegmentationsVisibility(ids); + }} + onDelete={id => { + SegmentationService.remove(id); + }} + onEdit={id => { + segmentationEditHandler({ + id, + servicesManager, + }); + }} + /> + ) : null} +
+ {tmtvValue !== null ? ( +
+ + {'TMTV:'} + +
{`${tmtvValue} mL`}
+
+ ) : null} + +
+
{ + // navigate to a url in a new tab + window.open( + 'https://github.com/OHIF/Viewers/blob/feat/segmentation-service/modes/tmtv/README.md', + '_blank' + ); + }} + > + + {'About'} +
+
+ ); +} + +PanelRoiThresholdSegmentation.propTypes = { + commandsManager: PropTypes.shape({ + runCommand: PropTypes.func.isRequired, + }), + servicesManager: PropTypes.shape({ + services: PropTypes.shape({ + SegmentationService: PropTypes.shape({ + getSegmentation: PropTypes.func.isRequired, + getSegmentations: PropTypes.func.isRequired, + toggleSegmentationsVisibility: PropTypes.func.isRequired, + subscribe: PropTypes.func.isRequired, + EVENTS: PropTypes.object.isRequired, + VALUE_TYPES: PropTypes.object.isRequired, + }).isRequired, + }).isRequired, + }).isRequired, +}; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx new file mode 100644 index 00000000000..377e79a8401 --- /dev/null +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { Input, Select, Button, ButtonGroup } from '@ohif/ui'; +import { useTranslation } from 'react-i18next'; + +export const ROI_STAT = 'roi_stat'; +const RANGE = 'range'; + +const options = [ + { value: ROI_STAT, label: 'Max', placeHolder: 'Max' }, + { value: RANGE, label: 'Range', placeHolder: 'Range' }, +]; + +function ROIThresholdConfiguration({ config, dispatch, runCommand }) { + const { t } = useTranslation('ROIThresholdConfiguration'); + + return ( +
+
+
+ { + dispatch({ + type: 'setWeight', + payload: e.target.value, + }); + }} + /> + )} + {config.strategy !== ROI_STAT && ( +
+ { + dispatch({ + type: 'setThreshold', + payload: { + lower: e.target.value, + }, + }); + }} + /> + { + dispatch({ + type: 'setThreshold', + payload: { + upper: e.target.value, + }, + }); + }} + /> +
+ )} +
+ ); +} + +export default ROIThresholdConfiguration; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/index.ts b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/index.ts new file mode 100644 index 00000000000..22404ee9861 --- /dev/null +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/index.ts @@ -0,0 +1,3 @@ +import PanelROIThresholdSegmentation from './PanelROIThresholdSegmentation'; + +export default PanelROIThresholdSegmentation; diff --git a/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/segmentationEditHandler.tsx b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/segmentationEditHandler.tsx new file mode 100644 index 00000000000..56aeb74efe1 --- /dev/null +++ b/extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/segmentationEditHandler.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Input, Dialog } from '@ohif/ui'; + +function segmentationItemEditHandler({ id, servicesManager }) { + const { SegmentationService, UIDialogService } = servicesManager.services; + + const segmentation = SegmentationService.getSegmentation(id); + + const onSubmitHandler = ({ action, value }) => { + switch (action.id) { + case 'save': { + SegmentationService.addOrUpdateSegmentation( + id, + { + ...segmentation, + ...value, + }, + true + ); + } + } + UIDialogService.dismiss({ id: 'enter-annotation' }); + }; + + UIDialogService.create({ + id: 'enter-annotation', + centralize: true, + isDraggable: false, + showOverlay: true, + content: Dialog, + contentProps: { + title: 'Enter your annotation', + noCloseButton: true, + value: { label: segmentation.label || '' }, + body: ({ value, setValue }) => { + const onChangeHandler = event => { + event.persist(); + setValue(value => ({ ...value, label: event.target.value })); + }; + + const onKeyPressHandler = event => { + if (event.key === 'Enter') { + onSubmitHandler({ value, action: { id: 'save' } }); + } + }; + return ( +
+ +
+ ); + }, + actions: [ + // temp: swap button types until colors are updated + { id: 'cancel', text: 'Cancel', type: 'primary' }, + { id: 'save', text: 'Save', type: 'secondary' }, + ], + onSubmit: onSubmitHandler, + }, + }); +} + +export default segmentationItemEditHandler; diff --git a/extensions/tmtv/src/Panels/index.tsx b/extensions/tmtv/src/Panels/index.tsx new file mode 100644 index 00000000000..0fa6418fdf4 --- /dev/null +++ b/extensions/tmtv/src/Panels/index.tsx @@ -0,0 +1,4 @@ +import PanelPetSUV from './PanelPetSUV'; +import PanelROIThresholdSegmentation from './PanelROIThresholdSegmentation'; + +export { PanelPetSUV, PanelROIThresholdSegmentation }; diff --git a/extensions/tmtv/src/commandsModule.js b/extensions/tmtv/src/commandsModule.js new file mode 100644 index 00000000000..e2633cca8d7 --- /dev/null +++ b/extensions/tmtv/src/commandsModule.js @@ -0,0 +1,696 @@ +import { vec3 } from 'gl-matrix'; +import OHIF from '@ohif/core'; +import * as cs from '@cornerstonejs/core'; +import * as csTools from '@cornerstonejs/tools'; +import { classes } from '@ohif/core'; +import getThresholdValues from './utils/getThresholdValue'; +import calculateSuvPeak from './utils/calculateSUVPeak'; +import calculateTMTV from './utils/calculateTMTV'; +import createAndDownloadTMTVReport from './utils/createAndDownloadTMTVReport'; + +import dicomRTAnnotationExport from './utils/dicomRTAnnotationExport/RTStructureSet'; + +const metadataProvider = classes.MetadataProvider; +const RECTANGLE_ROI_THRESHOLD_MANUAL = 'RectangleROIStartEndThreshold'; + +const commandsModule = ({ + servicesManager, + commandsManager, + extensionManager, +}) => { + const { + ViewportGridService, + UINotificationService, + DisplaySetService, + HangingProtocolService, + ToolGroupService, + CornerstoneViewportService, + } = servicesManager.services; + + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' + ); + + const { getEnabledElement } = utilityModule.exports; + + function _getActiveViewportsEnabledElement() { + const { activeViewportIndex } = ViewportGridService.getState(); + const { element } = getEnabledElement(activeViewportIndex) || {}; + const enabledElement = cs.getEnabledElement(element); + return enabledElement; + } + + function _getMatchedViewportsToolGroupIds() { + const [matchedViewports] = HangingProtocolService.getState(); + const toolGroupIds = []; + matchedViewports.forEach(({ viewportOptions }) => { + const { toolGroupId } = viewportOptions; + if (toolGroupIds.indexOf(toolGroupId) === -1) { + toolGroupIds.push(toolGroupId); + } + }); + + return toolGroupIds; + } + + const actions = { + getMatchingPTDisplaySet: () => { + // Todo: this is assuming that the hanging protocol has successfully matched + // the correct PT. For future, we should have a way to filter out the PTs + // that are in the viewer layout (but then we have the problem of the attenuation + // corrected PT vs the non-attenuation correct PT) + const matches = HangingProtocolService.getDisplaySetsMatchDetails(); + + const matchedSeriesInstanceUIDs = Array.from(matches.values()).map( + ({ SeriesInstanceUID }) => SeriesInstanceUID + ); + + let ptDisplaySet = null; + for (const SeriesInstanceUID of matchedSeriesInstanceUIDs) { + const displaySets = DisplaySetService.getDisplaySetsForSeries( + SeriesInstanceUID + ); + + if (!displaySets || displaySets.length === 0) { + continue; + } + + const displaySet = displaySets[0]; + if (displaySet.Modality !== 'PT') { + continue; + } + + ptDisplaySet = displaySet; + } + + return ptDisplaySet; + }, + getPTMetadata: ({ ptDisplaySet }) => { + const dataSource = extensionManager.getDataSources()[0]; + const imageIds = dataSource.getImageIdsForDisplaySet(ptDisplaySet); + + const firstImageId = imageIds[0]; + const instance = metadataProvider.get('instance', firstImageId); + if (instance.Modality !== 'PT') { + return; + } + + const metadata = { + SeriesTime: instance.SeriesTime, + Modality: instance.Modality, + PatientSex: instance.PatientSex, + PatientWeight: instance.PatientWeight, + RadiopharmaceuticalInformationSequence: { + RadionuclideTotalDose: + instance.RadiopharmaceuticalInformationSequence[0] + .RadionuclideTotalDose, + RadionuclideHalfLife: + instance.RadiopharmaceuticalInformationSequence[0] + .RadionuclideHalfLife, + RadiopharmaceuticalStartTime: + instance.RadiopharmaceuticalInformationSequence[0] + .RadiopharmaceuticalStartTime, + RadiopharmaceuticalStartDateTime: + instance.RadiopharmaceuticalInformationSequence[0] + .RadiopharmaceuticalStartDateTime, + }, + }; + + return metadata; + }, + createNewLabelmapFromPT: async () => { + // Create a segmentation of the same resolution as the source data + // using volumeLoader.createAndCacheDerivedVolume. + const ptDisplaySet = actions.getMatchingPTDisplaySet(); + + if (!ptDisplaySet) { + UINotificationService.error('No matching PT display set found'); + return; + } + + const segmentationId = await commandsManager.runCommand( + 'createSegmentationForDisplaySet', + { + displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID, + } + ); + + const toolGroupIds = _getMatchedViewportsToolGroupIds(); + + const representationType = + csTools.Enums.SegmentationRepresentations.Labelmap; + + for (const toolGroupId of toolGroupIds) { + await commandsManager.runCommand( + 'addSegmentationRepresentationToToolGroup', + { segmentationId, toolGroupId: toolGroupId, representationType } + ); + } + + return segmentationId; + }, + setSegmentationActiveForToolGroups: ({ segmentationId }) => { + const toolGroupIds = _getMatchedViewportsToolGroupIds(); + + toolGroupIds.forEach(toolGroupId => { + const segmentationRepresentations = csTools.segmentation.state.getSegmentationRepresentations( + toolGroupId + ); + + if (segmentationRepresentations.length === 0) { + return; + } + + // Todo: this finds the first segmentation representation that matches the segmentationId + // If there are two labelmap representations from the same segmentation, this will not work + const representation = segmentationRepresentations.find( + representation => representation.segmentationId === segmentationId + ); + + csTools.segmentation.activeSegmentation.setActiveSegmentationRepresentation( + toolGroupId, + representation.segmentationRepresentationUID + ); + }); + }, + thresholdSegmentationByRectangleROITool: ({ segmentationId, config }) => { + const segmentation = csTools.segmentation.state.getSegmentation( + segmentationId + ); + + const { representationData } = segmentation; + const { volumeId: segVolumeId } = representationData[ + csTools.Enums.SegmentationRepresentations.Labelmap + ]; + + const { referencedVolumeId } = cs.cache.getVolume(segVolumeId); + + const labelmapVolume = cs.cache.getVolume(segmentationId); + const referencedVolume = cs.cache.getVolume(referencedVolumeId); + + if (!referencedVolume) { + throw new Error('No Reference volume found'); + } + + if (!labelmapVolume) { + throw new Error('No Reference labelmap found'); + } + + const annotationUIDs = csTools.annotation.selection.getAnnotationsSelectedByToolName( + RECTANGLE_ROI_THRESHOLD_MANUAL + ); + + if (annotationUIDs.length === 0) { + UINotificationService.show({ + title: 'Commands Module', + message: 'No ROIThreshold Tool is Selected', + type: 'error', + }); + return; + } + + const { lower, upper } = getThresholdValues( + annotationUIDs, + referencedVolume, + config + ); + + const configToUse = { + lower, + upper, + overwrite: true, + }; + + return csTools.utilities.segmentation.rectangleROIThresholdVolumeByRange( + annotationUIDs, + labelmapVolume, + [referencedVolume], + configToUse + ); + }, + toggleSegmentationVisibility: ({ segmentationId }) => { + const toolGroupIds = _getMatchedViewportsToolGroupIds(); + + toolGroupIds.forEach(toolGroupId => { + const segmentationRepresentations = csTools.segmentation.state.getSegmentationRepresentations( + toolGroupId + ); + + if (segmentationRepresentations.length === 0) { + return; + } + + // Todo: this finds the first segmentation representation that matches the segmentationId + // If there are two labelmap representations from the same segmentation, this will not work + const representation = segmentationRepresentations.find( + representation => representation.segmentationId === segmentationId + ); + + const visibility = csTools.segmentation.config.visibility.getSegmentationVisibility( + toolGroupId, + representation.segmentationRepresentationUID + ); + + csTools.segmentation.config.visibility.setSegmentationVisibility( + toolGroupId, + representation.segmentationRepresentationUID, + !visibility + ); + }); + }, + calculateSuvPeak: ({ labelmap }) => { + const { referencedVolumeId } = labelmap; + + const referencedVolume = cs.cache.getVolume(referencedVolumeId); + + const annotationUIDs = csTools.annotation.selection.getAnnotationsSelectedByToolName( + RECTANGLE_ROI_THRESHOLD_MANUAL + ); + + const annotations = annotationUIDs.map(annotationUID => + csTools.annotation.state.getAnnotation(annotationUID) + ); + + const suvPeak = calculateSuvPeak(labelmap, referencedVolume, annotations); + return { + suvPeak: suvPeak.mean, + suvMax: suvPeak.max, + suvMaxIJK: suvPeak.maxIJK, + suvMaxLPS: suvPeak.maxLPS, + }; + }, + getLesionStats: ({ labelmap, segmentIndex = 1 }) => { + const { scalarData, spacing } = labelmap; + + const { scalarData: referencedScalarData } = cs.cache.getVolume( + labelmap.referencedVolumeId + ); + + let segmentationMax = -Infinity; + let segmentationMin = Infinity; + let segmentationValues = []; + + let voxelCount = 0; + for (let i = 0; i < scalarData.length; i++) { + if (scalarData[i] === segmentIndex) { + const value = referencedScalarData[i]; + segmentationValues.push(value); + if (value > segmentationMax) { + segmentationMax = value; + } + if (value < segmentationMin) { + segmentationMin = value; + } + voxelCount++; + } + } + + const stats = { + minValue: segmentationMin, + maxValue: segmentationMax, + meanValue: segmentationValues.reduce((a, b) => a + b, 0) / voxelCount, + stdValue: Math.sqrt( + segmentationValues.reduce((a, b) => a + b * b, 0) / voxelCount - + segmentationValues.reduce((a, b) => a + b, 0) / voxelCount ** 2 + ), + volume: voxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3, + }; + + return stats; + }, + calculateLesionGlycolysis: ({ lesionStats }) => { + const { meanValue, volume } = lesionStats; + + return { + lesionGlyoclysisStats: volume * meanValue, + }; + }, + calculateTMTV: ({ segmentations }) => { + const labelmaps = commandsManager.runCommand('getLabelmapVolumes', { + segmentations, + }); + + if (!labelmaps.length) { + return; + } + + return calculateTMTV(labelmaps); + }, + exportTMTVReportCSV: ({ segmentations, tmtv, config }) => { + const segReport = commandsManager.runCommand('getSegmentationCSVReport', { + segmentations, + }); + + const tlg = actions.getTotalLesionGlycolysis({ segmentations }); + const additionalReportRows = [ + { key: 'Total Metabolic Tumor Volume', value: { tmtv } }, + { key: 'Total Lesion Glycolysis', value: { tlg: tlg.toFixed(4) } }, + { key: 'Threshold Configuration', value: { ...config } }, + ]; + + createAndDownloadTMTVReport(segReport, additionalReportRows); + }, + getTotalLesionGlycolysis: ({ segmentations }) => { + const labelmapVolumes = commandsManager.runCommand('getLabelmapVolumes', { + segmentations, + }); + + let mergedLabelmap; + // merge labelmap will through an error if labels maps are not the same size + // or same direction or .... + try { + mergedLabelmap = csTools.utilities.segmentation.createMergedLabelmapForIndex( + labelmapVolumes + ); + } catch (e) { + console.error('commandsModule::getTotalLesionGlycolysis', e); + return; + } + + // grabbing the first labelmap referenceVolume since it will be the same for all + const { referencedVolumeId, spacing } = labelmapVolumes[0]; + + if (!referencedVolumeId) { + console.error( + 'commandsModule::getTotalLesionGlycolysis:No referencedVolumeId found' + ); + } + + const ptVolume = cs.cache.getVolume(referencedVolumeId); + const mergedLabelData = mergedLabelmap.scalarData; + + if (mergedLabelData.length !== ptVolume.scalarData.length) { + console.error( + 'commandsModule::getTotalLesionGlycolysis:Labelmap and ptVolume are not the same size' + ); + } + + let suv = 0; + let totalLesionVoxelCount = 0; + for (let i = 0; i < mergedLabelData.length; i++) { + // if not background + if (mergedLabelData[i] !== 0) { + suv += ptVolume.scalarData[i]; + totalLesionVoxelCount += 1; + } + } + + // Average SUV for the merged labelmap + const averageSuv = suv / totalLesionVoxelCount; + + // total Lesion Glycolysis [suv * ml] + return ( + averageSuv * + totalLesionVoxelCount * + spacing[0] * + spacing[1] * + spacing[2] * + 1e-3 + ); + }, + + setStartSliceForROIThresholdTool: () => { + const { viewport } = _getActiveViewportsEnabledElement(); + const { focalPoint, viewPlaneNormal } = viewport.getCamera(); + + const selectedAnnotationUIDs = csTools.annotation.selection.getAnnotationsSelectedByToolName( + RECTANGLE_ROI_THRESHOLD_MANUAL + ); + + const annotationUID = selectedAnnotationUIDs[0]; + + const annotation = csTools.annotation.state.getAnnotation(annotationUID); + + const { handles } = annotation.data; + const { points } = handles; + + // get the current slice Index + const sliceIndex = viewport.getCurrentImageIdIndex(); + annotation.data.startSlice = sliceIndex; + + // distance between camera focal point and each point on the rectangle + const newPoints = points.map(point => { + const distance = vec3.create(); + vec3.subtract(distance, focalPoint, point); + // distance in the direction of the viewPlaneNormal + const distanceInViewPlane = vec3.dot(distance, viewPlaneNormal); + // new point is current point minus distanceInViewPlane + const newPoint = vec3.create(); + vec3.scaleAndAdd(newPoint, point, viewPlaneNormal, distanceInViewPlane); + + return newPoint; + // + }); + + handles.points = newPoints; + // IMPORTANT: invalidate the toolData for the cached stat to get updated + // and re-calculate the projection points + annotation.invalidated = true; + viewport.render(); + }, + setEndSliceForROIThresholdTool: () => { + const { viewport } = _getActiveViewportsEnabledElement(); + + const selectedAnnotationUIDs = csTools.annotation.selection.getAnnotationsSelectedByToolName( + RECTANGLE_ROI_THRESHOLD_MANUAL + ); + + const annotationUID = selectedAnnotationUIDs[0]; + + const annotation = csTools.annotation.state.getAnnotation(annotationUID); + + // get the current slice Index + const sliceIndex = viewport.getCurrentImageIdIndex(); + annotation.data.endSlice = sliceIndex; + + // IMPORTANT: invalidate the toolData for the cached stat to get updated + // and re-calculate the projection points + annotation.invalidated = true; + + viewport.render(); + }, + createTMTVRTReport: () => { + // get all Rectangle ROI annotation + const stateManager = csTools.annotation.state.getDefaultAnnotationManager(); + + const annotations = []; + + Object.keys(stateManager.annotations).forEach(frameOfReferenceUID => { + const forAnnotations = stateManager.annotations[frameOfReferenceUID]; + const ROIAnnotations = forAnnotations[RECTANGLE_ROI_THRESHOLD_MANUAL]; + annotations.push(...ROIAnnotations); + }); + + commandsManager.runCommand('exportRTReportForAnnotations', { + annotations, + }); + }, + getSegmentationCSVReport: ({ segmentations }) => { + if (!segmentations || !segmentations.length) { + segmentations = SegmentationService.getSegmentations(); + } + + let report = {}; + + for (const segmentation of segmentations) { + const { id, label, data } = segmentation; + + const segReport = { id, label }; + + if (!data) { + report[id] = segReport; + continue; + } + + Object.keys(data).forEach(key => { + if (typeof data[key] !== 'object') { + segReport[key] = data[key]; + } else { + Object.keys(data[key]).forEach(subKey => { + const newKey = `${key}_${subKey}`; + segReport[newKey] = data[key][subKey]; + }); + } + }); + + const labelmapVolume = cornerstone.cache.getVolume(id); + + if (!labelmapVolume) { + report[id] = segReport; + continue; + } + + const referencedVolumeId = labelmapVolume.referencedVolumeId; + segReport.referencedVolumeId = referencedVolumeId; + + const referencedVolume = cornerstone.cache.getVolume( + referencedVolumeId + ); + + if (!referencedVolume) { + report[id] = segReport; + continue; + } + + if (!referencedVolume.imageIds || !referencedVolume.imageIds.length) { + report[id] = segReport; + continue; + } + + const firstImageId = referencedVolume.imageIds[0]; + const instance = OHIF.classes.MetadataProvider.get( + 'instance', + firstImageId + ); + + if (!instance) { + report[id] = segReport; + continue; + } + + report[id] = { + ...segReport, + PatientID: instance.PatientID, + PatientName: instance.PatientName.Alphabetic, + StudyInstanceUID: instance.StudyInstanceUID, + SeriesInstanceUID: instance.SeriesInstanceUID, + StudyDate: instance.StudyDate, + }; + } + + return report; + }, + exportRTReportForAnnotations: ({ annotations }) => { + dicomRTAnnotationExport(annotations); + }, + setFusionPTColormap: ({ toolGroupId, colormap }) => { + const toolGroup = ToolGroupService.getToolGroup(toolGroupId); + + const ptDisplaySet = actions.getMatchingPTDisplaySet(); + + if (!ptDisplaySet) { + return; + } + + const fusionViewportIds = toolGroup.getViewportIds(); + + let viewports = []; + fusionViewportIds.forEach(viewportId => { + const viewportInfo = CornerstoneViewportService.getViewportInfo( + viewportId + ); + + const viewportIndex = viewportInfo.getViewportIndex(); + commandsManager.runCommand('setViewportColormap', { + viewportIndex, + displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID, + colormap, + }); + + viewports.push( + CornerstoneViewportService.getCornerstoneViewport(viewportId) + ); + }); + + viewports.forEach(viewport => { + viewport.render(); + }); + }, + }; + + const definitions = { + setEndSliceForROIThresholdTool: { + commandFn: actions.setEndSliceForROIThresholdTool, + storeContexts: [], + options: {}, + }, + setStartSliceForROIThresholdTool: { + commandFn: actions.setStartSliceForROIThresholdTool, + storeContexts: [], + options: {}, + }, + getMatchingPTDisplaySet: { + commandFn: actions.getMatchingPTDisplaySet, + storeContexts: [], + options: {}, + }, + getPTMetadata: { + commandFn: actions.getPTMetadata, + storeContexts: [], + options: {}, + }, + createNewLabelmapFromPT: { + commandFn: actions.createNewLabelmapFromPT, + storeContexts: [], + options: {}, + }, + setSegmentationActiveForToolGroups: { + commandFn: actions.setSegmentationActiveForToolGroups, + storeContexts: [], + options: {}, + }, + thresholdSegmentationByRectangleROITool: { + commandFn: actions.thresholdSegmentationByRectangleROITool, + storeContexts: [], + options: {}, + }, + toggleSegmentationVisibility: { + commandFn: actions.toggleSegmentationVisibility, + storeContexts: [], + options: {}, + }, + getTotalLesionGlycolysis: { + commandFn: actions.getTotalLesionGlycolysis, + storeContexts: [], + options: {}, + }, + calculateSuvPeak: { + commandFn: actions.calculateSuvPeak, + storeContexts: [], + options: {}, + }, + getLesionStats: { + commandFn: actions.getLesionStats, + storeContexts: [], + options: {}, + }, + calculateTMTV: { + commandFn: actions.calculateTMTV, + storeContexts: [], + options: {}, + }, + exportTMTVReportCSV: { + commandFn: actions.exportTMTVReportCSV, + storeContexts: [], + options: {}, + }, + createTMTVRTReport: { + commandFn: actions.createTMTVRTReport, + storeContexts: [], + options: {}, + }, + getSegmentationCSVReport: { + commandFn: actions.getSegmentationCSVReport, + storeContexts: [], + options: {}, + }, + exportRTReportForAnnotations: { + commandFn: actions.exportRTReportForAnnotations, + storeContexts: [], + options: {}, + }, + setFusionPTColormap: { + commandFn: actions.setFusionPTColormap, + storeContexts: [], + options: {}, + }, + }; + + return { + actions, + definitions, + defaultContext: 'TMTV:CORNERSTONE', + }; +}; + +export default commandsModule; diff --git a/extensions/tmtv/src/getHangingProtocolModule.js b/extensions/tmtv/src/getHangingProtocolModule.js new file mode 100644 index 00000000000..c11a4b9f674 --- /dev/null +++ b/extensions/tmtv/src/getHangingProtocolModule.js @@ -0,0 +1,601 @@ +const ptCT = { + id: 'test', + locked: true, + hasUpdatedPriorsInformation: false, + name: 'Default', + createdDate: '2021-02-23T19:22:08.894Z', + modifiedDate: '2021-02-23T19:22:08.894Z', + availableTo: {}, + editableBy: {}, + toolGroupIds: [ + 'ctToolGroup', + 'ptToolGroup', + 'fusionToolGroup', + 'mipToolGroup', + ], + imageLoadStrategy: 'interleaveTopToBottom', // "default" , "interleaveTopToBottom", "interleaveCenter" + protocolMatchingRules: [ + { + id: 'wauZK2QNEfDPwcAQo', + weight: 1, + attribute: 'StudyInstanceUID', + constraint: { + contains: { + value: '1.3.6.1.4.', + }, + }, + required: false, + }, + { + id: 'wauZK2QNEfDPwcAQo', + weight: 1, + attribute: 'StudyDescription', + constraint: { + contains: { + value: 'PETCT', + }, + }, + required: false, + }, + { + id: 'wauZK2QNEfDPwcAQo', + weight: 1, + attribute: 'StudyDescription', + constraint: { + contains: { + value: 'PET/CT', + }, + }, + required: false, + }, + ], + stages: [ + { + id: 'hYbmMy3b7pz7GLiaT', + name: 'default', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 3, + columns: 4, + layoutOptions: [ + { + x: 0, + y: 0, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 1 / 4, + y: 0, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 2 / 4, + y: 0, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 0, + y: 1 / 3, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 1 / 4, + y: 1 / 3, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 2 / 4, + y: 1 / 3, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 0, + y: 2 / 3, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 1 / 4, + y: 2 / 3, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 2 / 4, + y: 2 / 3, + width: 1 / 4, + height: 1 / 3, + }, + { + x: 3 / 4, + y: 0, + width: 1 / 4, + height: 1, + }, + ], + }, + }, + displaySets: [ + { + id: 'ctDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'Modality', + constraint: { + equals: { + value: 'CT', + }, + }, + required: true, + }, + { + id: 'vSjk7NCYjtdS3XZAw', + weight: 1, + attribute: 'SeriesNumber', + constraint: { + equals: { + value: '4', + }, + }, + required: false, + }, + { + id: 'vSjk7NCYjtdS3XZAw', + weight: 1, + attribute: 'SeriesDescription', + constraint: { + contains: { + value: 'CT', + }, + }, + required: false, + }, + { + id: 'vSjk7NCYjtdS3XZAw', + weight: 1, + attribute: 'SeriesDescription', + constraint: { + contains: { + value: 'CT WB', + }, + }, + required: false, + }, + ], + studyMatchingRules: [], + }, + { + id: 'ptDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'Modality', + constraint: { + equals: { + value: 'PT', + }, + }, + required: true, + }, + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'SeriesDescription', + constraint: { + contains: { + value: 'Corrected', + }, + }, + required: false, + }, + { + id: 'GPEYqFLv2dwzCM322', + weight: 2, + attribute: 'SeriesDescription', + constraint: { + doesNotContain: { + value: 'Uncorrected', + }, + }, + required: false, + }, + ], + studyMatchingRules: [], + }, + ], + viewports: [ + { + viewportOptions: { + viewportId: 'ctAXIAL', + viewportType: 'volume', + orientation: 'axial', + toolGroupId: 'ctToolGroup', + initialImageOptions: { + // index: 5, + preset: 'first', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'axialSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ctSAGITTAL', + viewportType: 'volume', + orientation: 'sagittal', + toolGroupId: 'ctToolGroup', + syncGroups: [ + { + type: 'cameraPosition', + id: 'sagittalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ctCORONAL', + viewportType: 'volume', + orientation: 'coronal', + toolGroupId: 'ctToolGroup', + syncGroups: [ + { + type: 'cameraPosition', + id: 'coronalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ptAXIAL', + viewportType: 'volume', + background: [1, 1, 1], + orientation: 'axial', + toolGroupId: 'ptToolGroup', + initialImageOptions: { + // index: 5, + preset: 'first', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'axialSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + options: { + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + voiInverted: true, + }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ptSAGITTAL', + viewportType: 'volume', + orientation: 'sagittal', + background: [1, 1, 1], + toolGroupId: 'ptToolGroup', + syncGroups: [ + { + type: 'cameraPosition', + id: 'sagittalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + options: { + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + voiInverted: true, + }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ptCORONAL', + viewportType: 'volume', + orientation: 'coronal', + background: [1, 1, 1], + toolGroupId: 'ptToolGroup', + syncGroups: [ + { + type: 'cameraPosition', + id: 'coronalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ptWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + options: { + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + voiInverted: true, + }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'fusionAXIAL', + viewportType: 'volume', + orientation: 'axial', + toolGroupId: 'fusionToolGroup', + initialImageOptions: { + // index: 5, + preset: 'first', // 'first', 'last', 'middle' + }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'axialSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: false, + target: true, + }, + { + type: 'voi', + id: 'fusionWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + { + options: { + colormap: 'hsv', + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'fusionSAGITTAL', + viewportType: 'volume', + orientation: 'sagittal', + toolGroupId: 'fusionToolGroup', + // initialImageOptions: { + // index: 180, + // preset: 'middle', // 'first', 'last', 'middle' + // }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'sagittalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: false, + target: true, + }, + { + type: 'voi', + id: 'fusionWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + { + options: { + colormap: 'hsv', + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'fusionCoronal', + viewportType: 'volume', + orientation: 'coronal', + toolGroupId: 'fusionToolGroup', + // initialImageOptions: { + // index: 180, + // preset: 'middle', // 'first', 'last', 'middle' + // }, + syncGroups: [ + { + type: 'cameraPosition', + id: 'coronalSync', + source: true, + target: true, + }, + { + type: 'voi', + id: 'ctWLSync', + source: false, + target: true, + }, + { + type: 'voi', + id: 'fusionWLSync', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + { + options: { + colormap: 'hsv', + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + }, + id: 'ptDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'mipSagittal', + viewportType: 'volume', + orientation: 'sagittal', + background: [1, 1, 1], + toolGroupId: 'mipToolGroup', + // Custom props can be used to set custom properties which extensions + // can react on. + customViewportOptions: { + // We use viewportDisplay to filter the viewports which are displayed + // in mip and we set the scrollbar according to their rotation index + // in the cornerstone extension. + hideOverlays: true, + }, + }, + displaySets: [ + { + options: { + blendMode: 'MIP', + slabThickness: 'fullVolume', + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + voiInverted: true, + }, + id: 'ptDisplaySet', + }, + ], + }, + ], + createdDate: '2021-02-23T18:32:42.850Z', + }, + ], + numberOfPriorsReferenced: -1, +}; + +function getHangingProtocolModule() { + return [ + { + name: 'ptCT', + protocols: [ptCT], + }, + ]; +} + +export default getHangingProtocolModule; diff --git a/extensions/tmtv/src/getPanelModule.tsx b/extensions/tmtv/src/getPanelModule.tsx new file mode 100644 index 00000000000..f8d85fb5bc5 --- /dev/null +++ b/extensions/tmtv/src/getPanelModule.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { PanelPetSUV, PanelROIThresholdSegmentation } from './Panels'; + +// TODO: +// - No loading UI exists yet +// - cancel promises when component is destroyed +// - show errors in UI for thumbnails if promise fails + +function getPanelModule({ + commandsManager, + extensionManager, + servicesManager, +}) { + const wrappedPanelPetSuv = () => { + return ( + + ); + }; + + const wrappedROIThresholdSeg = () => { + return ( + + ); + }; + + return [ + { + name: 'petSUV', + iconName: 'edit-patient', + iconLabel: 'PET SUV', + label: 'PET-SUV', + component: wrappedPanelPetSuv, + }, + { + name: 'ROIThresholdSeg', + iconName: 'tool-create-threshold', + iconLabel: 'Threshold Seg', + label: 'Threshold-Seg', + component: wrappedROIThresholdSeg, + }, + ]; +} + +export default getPanelModule; diff --git a/extensions/tmtv/src/id.js b/extensions/tmtv/src/id.js new file mode 100644 index 00000000000..ebe5acd98ae --- /dev/null +++ b/extensions/tmtv/src/id.js @@ -0,0 +1,5 @@ +import packageJson from '../package.json'; + +const id = packageJson.name; + +export { id }; diff --git a/extensions/tmtv/src/index.tsx b/extensions/tmtv/src/index.tsx new file mode 100644 index 00000000000..26628b505c9 --- /dev/null +++ b/extensions/tmtv/src/index.tsx @@ -0,0 +1,34 @@ +import { id } from './id'; +import getHangingProtocolModule from './getHangingProtocolModule'; +import getPanelModule from './getPanelModule'; +import init from './init'; +import commandsModule from './commandsModule'; + +/** + * + */ +const tmtvExtension = { + /** + * Only required property. Should be a unique value across all extensions. + */ + id, + preRegistration({ + servicesManager, + commandsManager, + extensionManager, + configuration = {}, + }) { + init({ servicesManager, commandsManager, extensionManager, configuration }); + }, + getPanelModule, + getHangingProtocolModule, + getCommandsModule({ servicesManager, commandsManager, extensionManager }) { + return commandsModule({ + servicesManager, + commandsManager, + extensionManager, + }); + }, +}; + +export default tmtvExtension; diff --git a/extensions/tmtv/src/init.js b/extensions/tmtv/src/init.js new file mode 100644 index 00000000000..72fa9af887e --- /dev/null +++ b/extensions/tmtv/src/init.js @@ -0,0 +1,56 @@ +import { + addTool, + RectangleROIStartEndThresholdTool, +} from '@cornerstonejs/tools'; + +import measurementServiceMappingsFactory from './utils/measurementServiceMappings/measurementServiceMappingsFactory'; +import colormaps from './utils/colormaps'; + +const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools'; +const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1'; +/** + * + * @param {Object} servicesManager + * @param {Object} configuration + * @param {Object|Array} configuration.csToolsConfig + */ +export default function init({ servicesManager, extensionManager }) { + const { + MeasurementService, + DisplaySetService, + CornerstoneViewportService, + } = servicesManager.services; + + addTool(RectangleROIStartEndThresholdTool); + + const { RectangleROIStartEndThreshold } = measurementServiceMappingsFactory( + MeasurementService, + DisplaySetService, + CornerstoneViewportService + ); + + const csTools3DVer1MeasurementSource = MeasurementService.getSource( + CORNERSTONE_3D_TOOLS_SOURCE_NAME, + CORNERSTONE_3D_TOOLS_SOURCE_VERSION + ); + + MeasurementService.addMapping( + csTools3DVer1MeasurementSource, + 'RectangleROIStartEndThreshold', + RectangleROIStartEndThreshold.matchingCriteria, + RectangleROIStartEndThreshold.toAnnotation, + RectangleROIStartEndThreshold.toMeasurement + ); + + initColormaps(extensionManager); +} + +function initColormaps(extensionManager) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' + ); + + const { registerColormap } = utilityModule.exports; + + colormaps.forEach(registerColormap); +} diff --git a/extensions/tmtv/src/utils/calculateSUVPeak.ts b/extensions/tmtv/src/utils/calculateSUVPeak.ts new file mode 100644 index 00000000000..982c4a18e4f --- /dev/null +++ b/extensions/tmtv/src/utils/calculateSUVPeak.ts @@ -0,0 +1,155 @@ +import { Types } from '@cornerstonejs/core'; +import { utilities } from '@cornerstonejs/tools'; +import { vec3 } from 'gl-matrix'; + +type AnnotationsForThresholding = { + data: { + handles: { + points: Types.Point3[]; + }; + cachedStats?: { + projectionPoints?: Types.Point3[][]; + }; + }; +}; + +/** + * This method calculates the SUV peak on a segmented ROI from a reference PET + * volume. If a rectangle annotation is provided, the peak is calculated within that + * rectangle. Otherwise, the calculation is performed on the entire volume which + * will be slower but same result. + * @param viewport Viewport to use for the calculation + * @param labelmap Labelmap from which the mask is taken + * @param referenceVolume PET volume to use for SUV calculation + * @param toolData [Optional] list of toolData to use for SUV calculation + * @param segmentIndex The index of the segment to use for masking + * @returns + */ +function calculateSuvPeak( + labelmap: Types.IImageVolume, + referenceVolume: Types.IImageVolume, + annotations?: AnnotationsForThresholding[], + segmentIndex = 1 +): { + max: number; + maxIJK: Types.Point3; + maxLPS: Types.Point3; + mean: number; +} { + if (referenceVolume.metadata.Modality !== 'PT') { + return; + } + + if (labelmap.scalarData.length !== referenceVolume.scalarData.length) { + throw new Error( + 'labelmap and referenceVolume must have the same number of pixels' + ); + } + + const { + scalarData: labelmapData, + dimensions, + imageData: labelmapImageData, + } = labelmap; + + const { + scalarData: referenceVolumeData, + imageData: referenceVolumeImageData, + } = referenceVolume; + + let boundsIJK; + // Todo: using the first annotation for now + if (annotations && annotations[0].data?.cachedStats) { + const { projectionPoints } = annotations[0].data.cachedStats; + const pointsToUse = [].concat(...projectionPoints); // cannot use flat() because of typescript compiler right now + + const rectangleCornersIJK = pointsToUse.map(world => { + const ijk = vec3.fromValues(0, 0, 0); + referenceVolumeImageData.worldToIndex(world, ijk); + return ijk as Types.Point3; + }); + + boundsIJK = utilities.boundingBox.getBoundingBoxAroundShape( + rectangleCornersIJK, + dimensions + ); + } + + let max = 0; + let maxIJK = [0, 0, 0]; + let maxLPS = [0, 0, 0]; + + const callback = ({ pointIJK, pointLPS }) => { + const offset = referenceVolumeImageData.computeOffsetIndex(pointIJK); + const value = labelmapData[offset]; + + if (value !== segmentIndex) { + return; + } + + const referenceValue = referenceVolumeData[offset]; + + if (referenceValue > max) { + max = referenceValue; + maxIJK = pointIJK; + maxLPS = pointLPS; + } + }; + + utilities.pointInShapeCallback( + labelmapImageData, + () => true, + callback, + boundsIJK + ); + + const direction = labelmapImageData + .getDirection() + .slice(0, 3) as Types.Point3; + + /** + * 2. Find the bottom and top of the great circle for the second sphere (1cc sphere) + * V = (4/3)πr3 + */ + const radius = Math.pow(1 / ((4 / 3) * Math.PI), 1 / 3) * 10; + const diameter = radius * 2; + + const secondaryCircleWorld = vec3.create(); + const bottomWorld = vec3.create(); + const topWorld = vec3.create(); + referenceVolumeImageData.indexToWorld(maxIJK, secondaryCircleWorld); + vec3.scaleAndAdd(bottomWorld, secondaryCircleWorld, direction, -diameter / 2); + vec3.scaleAndAdd(topWorld, secondaryCircleWorld, direction, diameter / 2); + const suvPeakCirclePoints = [bottomWorld, topWorld] as [ + Types.Point3, + Types.Point3 + ]; + + /** + * 3. Find the Mean and Max of the 1cc sphere centered on the suv Max of the previous + * sphere + */ + let count = 0; + let acc = 0; + const suvPeakMeanCallback = ({ value }) => { + acc += value; + count += 1; + }; + + utilities.pointInSurroundingSphereCallback( + referenceVolumeImageData, + suvPeakCirclePoints, + suvPeakMeanCallback + ); + + const mean = acc / count; + + return { + max, + maxIJK, + maxLPS, + mean, + }; +} + +export default calculateSuvPeak; diff --git a/extensions/tmtv/src/utils/calculateTMTV.ts b/extensions/tmtv/src/utils/calculateTMTV.ts new file mode 100644 index 00000000000..f09375c346e --- /dev/null +++ b/extensions/tmtv/src/utils/calculateTMTV.ts @@ -0,0 +1,44 @@ +import { Types } from '@cornerstonejs/core'; +import { utilities } from '@cornerstonejs/tools'; + +/** + * Given a list of labelmaps (with the possibility of overlapping regions), + * and a referenceVolume, it calculates the total metabolic tumor volume (TMTV) + * by flattening and rasterizing each segment into a single labelmap and summing + * the total number of volume voxels. It should be noted that for this calculation + * we do not double count voxels that are part of multiple labelmaps. + * @param {} labelmaps + * @param {number} segmentIndex + * @returns {number} TMTV in ml + */ +function calculateTMTV( + labelmaps: Array, + segmentIndex = 1 +): number { + const volumeId = 'mergedLabelmap'; + + const mergedLabelmap = utilities.segmentation.createMergedLabelmapForIndex( + labelmaps, + segmentIndex, + volumeId + ); + + const { imageData, spacing } = mergedLabelmap; + const values = imageData + .getPointData() + .getScalars() + .getData(); + + // count non-zero values inside the outputData, this would + // consider the overlapping regions to be only counted once + const numVoxels = values.reduce((acc, curr) => { + if (curr > 0) { + return acc + 1; + } + return acc; + }, 0); + + return 1e-3 * numVoxels * spacing[0] * spacing[1] * spacing[2]; +} + +export default calculateTMTV; diff --git a/extensions/tmtv/src/utils/colormaps/index.js b/extensions/tmtv/src/utils/colormaps/index.js new file mode 100644 index 00000000000..34761d25963 --- /dev/null +++ b/extensions/tmtv/src/utils/colormaps/index.js @@ -0,0 +1,9272 @@ +export default [ + { + ColorSpace: 'RGB', + Name: 'hot_iron', + RGBPoints: [ + 0.0, + 0.0039215686, + 0.0039215686, + 0.0156862745, + 0.00392156862745098, + 0.0039215686, + 0.0039215686, + 0.0156862745, + 0.00784313725490196, + 0.0039215686, + 0.0039215686, + 0.031372549, + 0.011764705882352941, + 0.0039215686, + 0.0039215686, + 0.0470588235, + 0.01568627450980392, + 0.0039215686, + 0.0039215686, + 0.062745098, + 0.0196078431372549, + 0.0039215686, + 0.0039215686, + 0.0784313725, + 0.023529411764705882, + 0.0039215686, + 0.0039215686, + 0.0941176471, + 0.027450980392156862, + 0.0039215686, + 0.0039215686, + 0.1098039216, + 0.03137254901960784, + 0.0039215686, + 0.0039215686, + 0.1254901961, + 0.03529411764705882, + 0.0039215686, + 0.0039215686, + 0.1411764706, + 0.0392156862745098, + 0.0039215686, + 0.0039215686, + 0.1568627451, + 0.043137254901960784, + 0.0039215686, + 0.0039215686, + 0.1725490196, + 0.047058823529411764, + 0.0039215686, + 0.0039215686, + 0.1882352941, + 0.050980392156862744, + 0.0039215686, + 0.0039215686, + 0.2039215686, + 0.054901960784313725, + 0.0039215686, + 0.0039215686, + 0.2196078431, + 0.05882352941176471, + 0.0039215686, + 0.0039215686, + 0.2352941176, + 0.06274509803921569, + 0.0039215686, + 0.0039215686, + 0.2509803922, + 0.06666666666666667, + 0.0039215686, + 0.0039215686, + 0.262745098, + 0.07058823529411765, + 0.0039215686, + 0.0039215686, + 0.2784313725, + 0.07450980392156863, + 0.0039215686, + 0.0039215686, + 0.2941176471, + 0.0784313725490196, + 0.0039215686, + 0.0039215686, + 0.3098039216, + 0.08235294117647059, + 0.0039215686, + 0.0039215686, + 0.3254901961, + 0.08627450980392157, + 0.0039215686, + 0.0039215686, + 0.3411764706, + 0.09019607843137255, + 0.0039215686, + 0.0039215686, + 0.3568627451, + 0.09411764705882353, + 0.0039215686, + 0.0039215686, + 0.3725490196, + 0.09803921568627451, + 0.0039215686, + 0.0039215686, + 0.3882352941, + 0.10196078431372549, + 0.0039215686, + 0.0039215686, + 0.4039215686, + 0.10588235294117647, + 0.0039215686, + 0.0039215686, + 0.4196078431, + 0.10980392156862745, + 0.0039215686, + 0.0039215686, + 0.4352941176, + 0.11372549019607843, + 0.0039215686, + 0.0039215686, + 0.4509803922, + 0.11764705882352942, + 0.0039215686, + 0.0039215686, + 0.4666666667, + 0.12156862745098039, + 0.0039215686, + 0.0039215686, + 0.4823529412, + 0.12549019607843137, + 0.0039215686, + 0.0039215686, + 0.4980392157, + 0.12941176470588237, + 0.0039215686, + 0.0039215686, + 0.5137254902, + 0.13333333333333333, + 0.0039215686, + 0.0039215686, + 0.5294117647, + 0.13725490196078433, + 0.0039215686, + 0.0039215686, + 0.5450980392, + 0.1411764705882353, + 0.0039215686, + 0.0039215686, + 0.5607843137, + 0.1450980392156863, + 0.0039215686, + 0.0039215686, + 0.5764705882, + 0.14901960784313725, + 0.0039215686, + 0.0039215686, + 0.5921568627, + 0.15294117647058825, + 0.0039215686, + 0.0039215686, + 0.6078431373, + 0.1568627450980392, + 0.0039215686, + 0.0039215686, + 0.6235294118, + 0.1607843137254902, + 0.0039215686, + 0.0039215686, + 0.6392156863, + 0.16470588235294117, + 0.0039215686, + 0.0039215686, + 0.6549019608, + 0.16862745098039217, + 0.0039215686, + 0.0039215686, + 0.6705882353, + 0.17254901960784313, + 0.0039215686, + 0.0039215686, + 0.6862745098, + 0.17647058823529413, + 0.0039215686, + 0.0039215686, + 0.7019607843, + 0.1803921568627451, + 0.0039215686, + 0.0039215686, + 0.7176470588, + 0.1843137254901961, + 0.0039215686, + 0.0039215686, + 0.7333333333, + 0.18823529411764706, + 0.0039215686, + 0.0039215686, + 0.7490196078, + 0.19215686274509805, + 0.0039215686, + 0.0039215686, + 0.7607843137, + 0.19607843137254902, + 0.0039215686, + 0.0039215686, + 0.7764705882, + 0.2, + 0.0039215686, + 0.0039215686, + 0.7921568627, + 0.20392156862745098, + 0.0039215686, + 0.0039215686, + 0.8078431373, + 0.20784313725490197, + 0.0039215686, + 0.0039215686, + 0.8235294118, + 0.21176470588235294, + 0.0039215686, + 0.0039215686, + 0.8392156863, + 0.21568627450980393, + 0.0039215686, + 0.0039215686, + 0.8549019608, + 0.2196078431372549, + 0.0039215686, + 0.0039215686, + 0.8705882353, + 0.2235294117647059, + 0.0039215686, + 0.0039215686, + 0.8862745098, + 0.22745098039215686, + 0.0039215686, + 0.0039215686, + 0.9019607843, + 0.23137254901960785, + 0.0039215686, + 0.0039215686, + 0.9176470588, + 0.23529411764705885, + 0.0039215686, + 0.0039215686, + 0.9333333333, + 0.23921568627450984, + 0.0039215686, + 0.0039215686, + 0.9490196078, + 0.24313725490196078, + 0.0039215686, + 0.0039215686, + 0.9647058824, + 0.24705882352941178, + 0.0039215686, + 0.0039215686, + 0.9803921569, + 0.25098039215686274, + 0.0039215686, + 0.0039215686, + 0.9960784314, + 0.2549019607843137, + 0.0039215686, + 0.0039215686, + 0.9960784314, + 0.25882352941176473, + 0.0156862745, + 0.0039215686, + 0.9803921569, + 0.2627450980392157, + 0.031372549, + 0.0039215686, + 0.9647058824, + 0.26666666666666666, + 0.0470588235, + 0.0039215686, + 0.9490196078, + 0.27058823529411763, + 0.062745098, + 0.0039215686, + 0.9333333333, + 0.27450980392156865, + 0.0784313725, + 0.0039215686, + 0.9176470588, + 0.2784313725490196, + 0.0941176471, + 0.0039215686, + 0.9019607843, + 0.2823529411764706, + 0.1098039216, + 0.0039215686, + 0.8862745098, + 0.28627450980392155, + 0.1254901961, + 0.0039215686, + 0.8705882353, + 0.2901960784313726, + 0.1411764706, + 0.0039215686, + 0.8549019608, + 0.29411764705882354, + 0.1568627451, + 0.0039215686, + 0.8392156863, + 0.2980392156862745, + 0.1725490196, + 0.0039215686, + 0.8235294118, + 0.30196078431372547, + 0.1882352941, + 0.0039215686, + 0.8078431373, + 0.3058823529411765, + 0.2039215686, + 0.0039215686, + 0.7921568627, + 0.30980392156862746, + 0.2196078431, + 0.0039215686, + 0.7764705882, + 0.3137254901960784, + 0.2352941176, + 0.0039215686, + 0.7607843137, + 0.3176470588235294, + 0.2509803922, + 0.0039215686, + 0.7490196078, + 0.3215686274509804, + 0.262745098, + 0.0039215686, + 0.7333333333, + 0.3254901960784314, + 0.2784313725, + 0.0039215686, + 0.7176470588, + 0.32941176470588235, + 0.2941176471, + 0.0039215686, + 0.7019607843, + 0.3333333333333333, + 0.3098039216, + 0.0039215686, + 0.6862745098, + 0.33725490196078434, + 0.3254901961, + 0.0039215686, + 0.6705882353, + 0.3411764705882353, + 0.3411764706, + 0.0039215686, + 0.6549019608, + 0.34509803921568627, + 0.3568627451, + 0.0039215686, + 0.6392156863, + 0.34901960784313724, + 0.3725490196, + 0.0039215686, + 0.6235294118, + 0.35294117647058826, + 0.3882352941, + 0.0039215686, + 0.6078431373, + 0.3568627450980392, + 0.4039215686, + 0.0039215686, + 0.5921568627, + 0.3607843137254902, + 0.4196078431, + 0.0039215686, + 0.5764705882, + 0.36470588235294116, + 0.4352941176, + 0.0039215686, + 0.5607843137, + 0.3686274509803922, + 0.4509803922, + 0.0039215686, + 0.5450980392, + 0.37254901960784315, + 0.4666666667, + 0.0039215686, + 0.5294117647, + 0.3764705882352941, + 0.4823529412, + 0.0039215686, + 0.5137254902, + 0.3803921568627451, + 0.4980392157, + 0.0039215686, + 0.4980392157, + 0.3843137254901961, + 0.5137254902, + 0.0039215686, + 0.4823529412, + 0.38823529411764707, + 0.5294117647, + 0.0039215686, + 0.4666666667, + 0.39215686274509803, + 0.5450980392, + 0.0039215686, + 0.4509803922, + 0.396078431372549, + 0.5607843137, + 0.0039215686, + 0.4352941176, + 0.4, + 0.5764705882, + 0.0039215686, + 0.4196078431, + 0.403921568627451, + 0.5921568627, + 0.0039215686, + 0.4039215686, + 0.40784313725490196, + 0.6078431373, + 0.0039215686, + 0.3882352941, + 0.4117647058823529, + 0.6235294118, + 0.0039215686, + 0.3725490196, + 0.41568627450980394, + 0.6392156863, + 0.0039215686, + 0.3568627451, + 0.4196078431372549, + 0.6549019608, + 0.0039215686, + 0.3411764706, + 0.4235294117647059, + 0.6705882353, + 0.0039215686, + 0.3254901961, + 0.42745098039215684, + 0.6862745098, + 0.0039215686, + 0.3098039216, + 0.43137254901960786, + 0.7019607843, + 0.0039215686, + 0.2941176471, + 0.43529411764705883, + 0.7176470588, + 0.0039215686, + 0.2784313725, + 0.4392156862745098, + 0.7333333333, + 0.0039215686, + 0.262745098, + 0.44313725490196076, + 0.7490196078, + 0.0039215686, + 0.2509803922, + 0.4470588235294118, + 0.7607843137, + 0.0039215686, + 0.2352941176, + 0.45098039215686275, + 0.7764705882, + 0.0039215686, + 0.2196078431, + 0.4549019607843137, + 0.7921568627, + 0.0039215686, + 0.2039215686, + 0.4588235294117647, + 0.8078431373, + 0.0039215686, + 0.1882352941, + 0.4627450980392157, + 0.8235294118, + 0.0039215686, + 0.1725490196, + 0.4666666666666667, + 0.8392156863, + 0.0039215686, + 0.1568627451, + 0.4705882352941177, + 0.8549019608, + 0.0039215686, + 0.1411764706, + 0.4745098039215686, + 0.8705882353, + 0.0039215686, + 0.1254901961, + 0.4784313725490197, + 0.8862745098, + 0.0039215686, + 0.1098039216, + 0.48235294117647065, + 0.9019607843, + 0.0039215686, + 0.0941176471, + 0.48627450980392156, + 0.9176470588, + 0.0039215686, + 0.0784313725, + 0.49019607843137253, + 0.9333333333, + 0.0039215686, + 0.062745098, + 0.49411764705882355, + 0.9490196078, + 0.0039215686, + 0.0470588235, + 0.4980392156862745, + 0.9647058824, + 0.0039215686, + 0.031372549, + 0.5019607843137255, + 0.9803921569, + 0.0039215686, + 0.0156862745, + 0.5058823529411764, + 0.9960784314, + 0.0039215686, + 0.0039215686, + 0.5098039215686274, + 0.9960784314, + 0.0156862745, + 0.0039215686, + 0.5137254901960784, + 0.9960784314, + 0.031372549, + 0.0039215686, + 0.5176470588235295, + 0.9960784314, + 0.0470588235, + 0.0039215686, + 0.5215686274509804, + 0.9960784314, + 0.062745098, + 0.0039215686, + 0.5254901960784314, + 0.9960784314, + 0.0784313725, + 0.0039215686, + 0.5294117647058824, + 0.9960784314, + 0.0941176471, + 0.0039215686, + 0.5333333333333333, + 0.9960784314, + 0.1098039216, + 0.0039215686, + 0.5372549019607843, + 0.9960784314, + 0.1254901961, + 0.0039215686, + 0.5411764705882353, + 0.9960784314, + 0.1411764706, + 0.0039215686, + 0.5450980392156862, + 0.9960784314, + 0.1568627451, + 0.0039215686, + 0.5490196078431373, + 0.9960784314, + 0.1725490196, + 0.0039215686, + 0.5529411764705883, + 0.9960784314, + 0.1882352941, + 0.0039215686, + 0.5568627450980392, + 0.9960784314, + 0.2039215686, + 0.0039215686, + 0.5607843137254902, + 0.9960784314, + 0.2196078431, + 0.0039215686, + 0.5647058823529412, + 0.9960784314, + 0.2352941176, + 0.0039215686, + 0.5686274509803921, + 0.9960784314, + 0.2509803922, + 0.0039215686, + 0.5725490196078431, + 0.9960784314, + 0.262745098, + 0.0039215686, + 0.5764705882352941, + 0.9960784314, + 0.2784313725, + 0.0039215686, + 0.5803921568627451, + 0.9960784314, + 0.2941176471, + 0.0039215686, + 0.5843137254901961, + 0.9960784314, + 0.3098039216, + 0.0039215686, + 0.5882352941176471, + 0.9960784314, + 0.3254901961, + 0.0039215686, + 0.592156862745098, + 0.9960784314, + 0.3411764706, + 0.0039215686, + 0.596078431372549, + 0.9960784314, + 0.3568627451, + 0.0039215686, + 0.6, + 0.9960784314, + 0.3725490196, + 0.0039215686, + 0.6039215686274509, + 0.9960784314, + 0.3882352941, + 0.0039215686, + 0.6078431372549019, + 0.9960784314, + 0.4039215686, + 0.0039215686, + 0.611764705882353, + 0.9960784314, + 0.4196078431, + 0.0039215686, + 0.615686274509804, + 0.9960784314, + 0.4352941176, + 0.0039215686, + 0.6196078431372549, + 0.9960784314, + 0.4509803922, + 0.0039215686, + 0.6235294117647059, + 0.9960784314, + 0.4666666667, + 0.0039215686, + 0.6274509803921569, + 0.9960784314, + 0.4823529412, + 0.0039215686, + 0.6313725490196078, + 0.9960784314, + 0.4980392157, + 0.0039215686, + 0.6352941176470588, + 0.9960784314, + 0.5137254902, + 0.0039215686, + 0.6392156862745098, + 0.9960784314, + 0.5294117647, + 0.0039215686, + 0.6431372549019608, + 0.9960784314, + 0.5450980392, + 0.0039215686, + 0.6470588235294118, + 0.9960784314, + 0.5607843137, + 0.0039215686, + 0.6509803921568628, + 0.9960784314, + 0.5764705882, + 0.0039215686, + 0.6549019607843137, + 0.9960784314, + 0.5921568627, + 0.0039215686, + 0.6588235294117647, + 0.9960784314, + 0.6078431373, + 0.0039215686, + 0.6627450980392157, + 0.9960784314, + 0.6235294118, + 0.0039215686, + 0.6666666666666666, + 0.9960784314, + 0.6392156863, + 0.0039215686, + 0.6705882352941176, + 0.9960784314, + 0.6549019608, + 0.0039215686, + 0.6745098039215687, + 0.9960784314, + 0.6705882353, + 0.0039215686, + 0.6784313725490196, + 0.9960784314, + 0.6862745098, + 0.0039215686, + 0.6823529411764706, + 0.9960784314, + 0.7019607843, + 0.0039215686, + 0.6862745098039216, + 0.9960784314, + 0.7176470588, + 0.0039215686, + 0.6901960784313725, + 0.9960784314, + 0.7333333333, + 0.0039215686, + 0.6941176470588235, + 0.9960784314, + 0.7490196078, + 0.0039215686, + 0.6980392156862745, + 0.9960784314, + 0.7607843137, + 0.0039215686, + 0.7019607843137254, + 0.9960784314, + 0.7764705882, + 0.0039215686, + 0.7058823529411765, + 0.9960784314, + 0.7921568627, + 0.0039215686, + 0.7098039215686275, + 0.9960784314, + 0.8078431373, + 0.0039215686, + 0.7137254901960784, + 0.9960784314, + 0.8235294118, + 0.0039215686, + 0.7176470588235294, + 0.9960784314, + 0.8392156863, + 0.0039215686, + 0.7215686274509804, + 0.9960784314, + 0.8549019608, + 0.0039215686, + 0.7254901960784313, + 0.9960784314, + 0.8705882353, + 0.0039215686, + 0.7294117647058823, + 0.9960784314, + 0.8862745098, + 0.0039215686, + 0.7333333333333333, + 0.9960784314, + 0.9019607843, + 0.0039215686, + 0.7372549019607844, + 0.9960784314, + 0.9176470588, + 0.0039215686, + 0.7411764705882353, + 0.9960784314, + 0.9333333333, + 0.0039215686, + 0.7450980392156863, + 0.9960784314, + 0.9490196078, + 0.0039215686, + 0.7490196078431373, + 0.9960784314, + 0.9647058824, + 0.0039215686, + 0.7529411764705882, + 0.9960784314, + 0.9803921569, + 0.0039215686, + 0.7568627450980392, + 0.9960784314, + 0.9960784314, + 0.0039215686, + 0.7607843137254902, + 0.9960784314, + 0.9960784314, + 0.0196078431, + 0.7647058823529411, + 0.9960784314, + 0.9960784314, + 0.0352941176, + 0.7686274509803922, + 0.9960784314, + 0.9960784314, + 0.0509803922, + 0.7725490196078432, + 0.9960784314, + 0.9960784314, + 0.0666666667, + 0.7764705882352941, + 0.9960784314, + 0.9960784314, + 0.0823529412, + 0.7803921568627451, + 0.9960784314, + 0.9960784314, + 0.0980392157, + 0.7843137254901961, + 0.9960784314, + 0.9960784314, + 0.1137254902, + 0.788235294117647, + 0.9960784314, + 0.9960784314, + 0.1294117647, + 0.792156862745098, + 0.9960784314, + 0.9960784314, + 0.1450980392, + 0.796078431372549, + 0.9960784314, + 0.9960784314, + 0.1607843137, + 0.8, + 0.9960784314, + 0.9960784314, + 0.1764705882, + 0.803921568627451, + 0.9960784314, + 0.9960784314, + 0.1921568627, + 0.807843137254902, + 0.9960784314, + 0.9960784314, + 0.2078431373, + 0.8117647058823529, + 0.9960784314, + 0.9960784314, + 0.2235294118, + 0.8156862745098039, + 0.9960784314, + 0.9960784314, + 0.2392156863, + 0.8196078431372549, + 0.9960784314, + 0.9960784314, + 0.2509803922, + 0.8235294117647058, + 0.9960784314, + 0.9960784314, + 0.2666666667, + 0.8274509803921568, + 0.9960784314, + 0.9960784314, + 0.2823529412, + 0.8313725490196079, + 0.9960784314, + 0.9960784314, + 0.2980392157, + 0.8352941176470589, + 0.9960784314, + 0.9960784314, + 0.3137254902, + 0.8392156862745098, + 0.9960784314, + 0.9960784314, + 0.3333333333, + 0.8431372549019608, + 0.9960784314, + 0.9960784314, + 0.3490196078, + 0.8470588235294118, + 0.9960784314, + 0.9960784314, + 0.3647058824, + 0.8509803921568627, + 0.9960784314, + 0.9960784314, + 0.3803921569, + 0.8549019607843137, + 0.9960784314, + 0.9960784314, + 0.3960784314, + 0.8588235294117647, + 0.9960784314, + 0.9960784314, + 0.4117647059, + 0.8627450980392157, + 0.9960784314, + 0.9960784314, + 0.4274509804, + 0.8666666666666667, + 0.9960784314, + 0.9960784314, + 0.4431372549, + 0.8705882352941177, + 0.9960784314, + 0.9960784314, + 0.4588235294, + 0.8745098039215686, + 0.9960784314, + 0.9960784314, + 0.4745098039, + 0.8784313725490196, + 0.9960784314, + 0.9960784314, + 0.4901960784, + 0.8823529411764706, + 0.9960784314, + 0.9960784314, + 0.5058823529, + 0.8862745098039215, + 0.9960784314, + 0.9960784314, + 0.5215686275, + 0.8901960784313725, + 0.9960784314, + 0.9960784314, + 0.537254902, + 0.8941176470588236, + 0.9960784314, + 0.9960784314, + 0.5529411765, + 0.8980392156862745, + 0.9960784314, + 0.9960784314, + 0.568627451, + 0.9019607843137255, + 0.9960784314, + 0.9960784314, + 0.5843137255, + 0.9058823529411765, + 0.9960784314, + 0.9960784314, + 0.6, + 0.9098039215686274, + 0.9960784314, + 0.9960784314, + 0.6156862745, + 0.9137254901960784, + 0.9960784314, + 0.9960784314, + 0.631372549, + 0.9176470588235294, + 0.9960784314, + 0.9960784314, + 0.6470588235, + 0.9215686274509803, + 0.9960784314, + 0.9960784314, + 0.6666666667, + 0.9254901960784314, + 0.9960784314, + 0.9960784314, + 0.6823529412, + 0.9294117647058824, + 0.9960784314, + 0.9960784314, + 0.6980392157, + 0.9333333333333333, + 0.9960784314, + 0.9960784314, + 0.7137254902, + 0.9372549019607843, + 0.9960784314, + 0.9960784314, + 0.7294117647, + 0.9411764705882354, + 0.9960784314, + 0.9960784314, + 0.7450980392, + 0.9450980392156864, + 0.9960784314, + 0.9960784314, + 0.7568627451, + 0.9490196078431372, + 0.9960784314, + 0.9960784314, + 0.7725490196, + 0.9529411764705882, + 0.9960784314, + 0.9960784314, + 0.7882352941, + 0.9568627450980394, + 0.9960784314, + 0.9960784314, + 0.8039215686, + 0.9607843137254903, + 0.9960784314, + 0.9960784314, + 0.8196078431, + 0.9647058823529413, + 0.9960784314, + 0.9960784314, + 0.8352941176, + 0.9686274509803922, + 0.9960784314, + 0.9960784314, + 0.8509803922, + 0.9725490196078431, + 0.9960784314, + 0.9960784314, + 0.8666666667, + 0.9764705882352941, + 0.9960784314, + 0.9960784314, + 0.8823529412, + 0.9803921568627451, + 0.9960784314, + 0.9960784314, + 0.8980392157, + 0.984313725490196, + 0.9960784314, + 0.9960784314, + 0.9137254902, + 0.9882352941176471, + 0.9960784314, + 0.9960784314, + 0.9294117647, + 0.9921568627450981, + 0.9960784314, + 0.9960784314, + 0.9450980392, + 0.996078431372549, + 0.9960784314, + 0.9960784314, + 0.9607843137, + 1.0, + 0.9960784314, + 0.9960784314, + 0.9607843137, + ], + }, + { + ColorSpace: 'RGB', + Name: 'red_hot', + RGBPoints: [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.00392156862745098, + 0.0, + 0.0, + 0.0, + 0.00784313725490196, + 0.0, + 0.0, + 0.0, + 0.011764705882352941, + 0.0, + 0.0, + 0.0, + 0.01568627450980392, + 0.0039215686, + 0.0039215686, + 0.0039215686, + 0.0196078431372549, + 0.0039215686, + 0.0039215686, + 0.0039215686, + 0.023529411764705882, + 0.0039215686, + 0.0039215686, + 0.0039215686, + 0.027450980392156862, + 0.0039215686, + 0.0039215686, + 0.0039215686, + 0.03137254901960784, + 0.0039215686, + 0.0039215686, + 0.0039215686, + 0.03529411764705882, + 0.0156862745, + 0.0, + 0.0, + 0.0392156862745098, + 0.0274509804, + 0.0, + 0.0, + 0.043137254901960784, + 0.0392156863, + 0.0, + 0.0, + 0.047058823529411764, + 0.0509803922, + 0.0, + 0.0, + 0.050980392156862744, + 0.062745098, + 0.0, + 0.0, + 0.054901960784313725, + 0.0784313725, + 0.0, + 0.0, + 0.05882352941176471, + 0.0901960784, + 0.0, + 0.0, + 0.06274509803921569, + 0.1058823529, + 0.0, + 0.0, + 0.06666666666666667, + 0.1176470588, + 0.0, + 0.0, + 0.07058823529411765, + 0.1294117647, + 0.0, + 0.0, + 0.07450980392156863, + 0.1411764706, + 0.0, + 0.0, + 0.0784313725490196, + 0.1529411765, + 0.0, + 0.0, + 0.08235294117647059, + 0.1647058824, + 0.0, + 0.0, + 0.08627450980392157, + 0.1764705882, + 0.0, + 0.0, + 0.09019607843137255, + 0.1882352941, + 0.0, + 0.0, + 0.09411764705882353, + 0.2039215686, + 0.0, + 0.0, + 0.09803921568627451, + 0.2156862745, + 0.0, + 0.0, + 0.10196078431372549, + 0.2274509804, + 0.0, + 0.0, + 0.10588235294117647, + 0.2392156863, + 0.0, + 0.0, + 0.10980392156862745, + 0.2549019608, + 0.0, + 0.0, + 0.11372549019607843, + 0.2666666667, + 0.0, + 0.0, + 0.11764705882352942, + 0.2784313725, + 0.0, + 0.0, + 0.12156862745098039, + 0.2901960784, + 0.0, + 0.0, + 0.12549019607843137, + 0.3058823529, + 0.0, + 0.0, + 0.12941176470588237, + 0.3176470588, + 0.0, + 0.0, + 0.13333333333333333, + 0.3294117647, + 0.0, + 0.0, + 0.13725490196078433, + 0.3411764706, + 0.0, + 0.0, + 0.1411764705882353, + 0.3529411765, + 0.0, + 0.0, + 0.1450980392156863, + 0.3647058824, + 0.0, + 0.0, + 0.14901960784313725, + 0.3764705882, + 0.0, + 0.0, + 0.15294117647058825, + 0.3882352941, + 0.0, + 0.0, + 0.1568627450980392, + 0.4039215686, + 0.0, + 0.0, + 0.1607843137254902, + 0.4156862745, + 0.0, + 0.0, + 0.16470588235294117, + 0.431372549, + 0.0, + 0.0, + 0.16862745098039217, + 0.4431372549, + 0.0, + 0.0, + 0.17254901960784313, + 0.4588235294, + 0.0, + 0.0, + 0.17647058823529413, + 0.4705882353, + 0.0, + 0.0, + 0.1803921568627451, + 0.4823529412, + 0.0, + 0.0, + 0.1843137254901961, + 0.4941176471, + 0.0, + 0.0, + 0.18823529411764706, + 0.5098039216, + 0.0, + 0.0, + 0.19215686274509805, + 0.5215686275, + 0.0, + 0.0, + 0.19607843137254902, + 0.5333333333, + 0.0, + 0.0, + 0.2, + 0.5450980392, + 0.0, + 0.0, + 0.20392156862745098, + 0.5568627451, + 0.0, + 0.0, + 0.20784313725490197, + 0.568627451, + 0.0, + 0.0, + 0.21176470588235294, + 0.5803921569, + 0.0, + 0.0, + 0.21568627450980393, + 0.5921568627, + 0.0, + 0.0, + 0.2196078431372549, + 0.6078431373, + 0.0, + 0.0, + 0.2235294117647059, + 0.6196078431, + 0.0, + 0.0, + 0.22745098039215686, + 0.631372549, + 0.0, + 0.0, + 0.23137254901960785, + 0.6431372549, + 0.0, + 0.0, + 0.23529411764705885, + 0.6588235294, + 0.0, + 0.0, + 0.23921568627450984, + 0.6705882353, + 0.0, + 0.0, + 0.24313725490196078, + 0.6823529412, + 0.0, + 0.0, + 0.24705882352941178, + 0.6941176471, + 0.0, + 0.0, + 0.25098039215686274, + 0.7098039216, + 0.0, + 0.0, + 0.2549019607843137, + 0.7215686275, + 0.0, + 0.0, + 0.25882352941176473, + 0.7333333333, + 0.0, + 0.0, + 0.2627450980392157, + 0.7450980392, + 0.0, + 0.0, + 0.26666666666666666, + 0.7568627451, + 0.0, + 0.0, + 0.27058823529411763, + 0.768627451, + 0.0, + 0.0, + 0.27450980392156865, + 0.7843137255, + 0.0, + 0.0, + 0.2784313725490196, + 0.7960784314, + 0.0, + 0.0, + 0.2823529411764706, + 0.8117647059, + 0.0, + 0.0, + 0.28627450980392155, + 0.8235294118, + 0.0, + 0.0, + 0.2901960784313726, + 0.8352941176, + 0.0, + 0.0, + 0.29411764705882354, + 0.8470588235, + 0.0, + 0.0, + 0.2980392156862745, + 0.862745098, + 0.0, + 0.0, + 0.30196078431372547, + 0.8745098039, + 0.0, + 0.0, + 0.3058823529411765, + 0.8862745098, + 0.0, + 0.0, + 0.30980392156862746, + 0.8980392157, + 0.0, + 0.0, + 0.3137254901960784, + 0.9137254902, + 0.0, + 0.0, + 0.3176470588235294, + 0.9254901961, + 0.0, + 0.0, + 0.3215686274509804, + 0.937254902, + 0.0, + 0.0, + 0.3254901960784314, + 0.9490196078, + 0.0, + 0.0, + 0.32941176470588235, + 0.9607843137, + 0.0, + 0.0, + 0.3333333333333333, + 0.968627451, + 0.0, + 0.0, + 0.33725490196078434, + 0.9803921569, + 0.0039215686, + 0.0, + 0.3411764705882353, + 0.9882352941, + 0.0078431373, + 0.0, + 0.34509803921568627, + 1.0, + 0.0117647059, + 0.0, + 0.34901960784313724, + 1.0, + 0.0235294118, + 0.0, + 0.35294117647058826, + 1.0, + 0.0352941176, + 0.0, + 0.3568627450980392, + 1.0, + 0.0470588235, + 0.0, + 0.3607843137254902, + 1.0, + 0.062745098, + 0.0, + 0.36470588235294116, + 1.0, + 0.0745098039, + 0.0, + 0.3686274509803922, + 1.0, + 0.0862745098, + 0.0, + 0.37254901960784315, + 1.0, + 0.0980392157, + 0.0, + 0.3764705882352941, + 1.0, + 0.1137254902, + 0.0, + 0.3803921568627451, + 1.0, + 0.1254901961, + 0.0, + 0.3843137254901961, + 1.0, + 0.137254902, + 0.0, + 0.38823529411764707, + 1.0, + 0.1490196078, + 0.0, + 0.39215686274509803, + 1.0, + 0.1647058824, + 0.0, + 0.396078431372549, + 1.0, + 0.1764705882, + 0.0, + 0.4, + 1.0, + 0.1882352941, + 0.0, + 0.403921568627451, + 1.0, + 0.2, + 0.0, + 0.40784313725490196, + 1.0, + 0.2156862745, + 0.0, + 0.4117647058823529, + 1.0, + 0.2274509804, + 0.0, + 0.41568627450980394, + 1.0, + 0.2392156863, + 0.0, + 0.4196078431372549, + 1.0, + 0.2509803922, + 0.0, + 0.4235294117647059, + 1.0, + 0.2666666667, + 0.0, + 0.42745098039215684, + 1.0, + 0.2784313725, + 0.0, + 0.43137254901960786, + 1.0, + 0.2901960784, + 0.0, + 0.43529411764705883, + 1.0, + 0.3019607843, + 0.0, + 0.4392156862745098, + 1.0, + 0.3176470588, + 0.0, + 0.44313725490196076, + 1.0, + 0.3294117647, + 0.0, + 0.4470588235294118, + 1.0, + 0.3411764706, + 0.0, + 0.45098039215686275, + 1.0, + 0.3529411765, + 0.0, + 0.4549019607843137, + 1.0, + 0.368627451, + 0.0, + 0.4588235294117647, + 1.0, + 0.3803921569, + 0.0, + 0.4627450980392157, + 1.0, + 0.3921568627, + 0.0, + 0.4666666666666667, + 1.0, + 0.4039215686, + 0.0, + 0.4705882352941177, + 1.0, + 0.4156862745, + 0.0, + 0.4745098039215686, + 1.0, + 0.4274509804, + 0.0, + 0.4784313725490197, + 1.0, + 0.4392156863, + 0.0, + 0.48235294117647065, + 1.0, + 0.4509803922, + 0.0, + 0.48627450980392156, + 1.0, + 0.4666666667, + 0.0, + 0.49019607843137253, + 1.0, + 0.4784313725, + 0.0, + 0.49411764705882355, + 1.0, + 0.4941176471, + 0.0, + 0.4980392156862745, + 1.0, + 0.5058823529, + 0.0, + 0.5019607843137255, + 1.0, + 0.5215686275, + 0.0, + 0.5058823529411764, + 1.0, + 0.5333333333, + 0.0, + 0.5098039215686274, + 1.0, + 0.5450980392, + 0.0, + 0.5137254901960784, + 1.0, + 0.5568627451, + 0.0, + 0.5176470588235295, + 1.0, + 0.568627451, + 0.0, + 0.5215686274509804, + 1.0, + 0.5803921569, + 0.0, + 0.5254901960784314, + 1.0, + 0.5921568627, + 0.0, + 0.5294117647058824, + 1.0, + 0.6039215686, + 0.0, + 0.5333333333333333, + 1.0, + 0.6196078431, + 0.0, + 0.5372549019607843, + 1.0, + 0.631372549, + 0.0, + 0.5411764705882353, + 1.0, + 0.6431372549, + 0.0, + 0.5450980392156862, + 1.0, + 0.6549019608, + 0.0, + 0.5490196078431373, + 1.0, + 0.6705882353, + 0.0, + 0.5529411764705883, + 1.0, + 0.6823529412, + 0.0, + 0.5568627450980392, + 1.0, + 0.6941176471, + 0.0, + 0.5607843137254902, + 1.0, + 0.7058823529, + 0.0, + 0.5647058823529412, + 1.0, + 0.7215686275, + 0.0, + 0.5686274509803921, + 1.0, + 0.7333333333, + 0.0, + 0.5725490196078431, + 1.0, + 0.7450980392, + 0.0, + 0.5764705882352941, + 1.0, + 0.7568627451, + 0.0, + 0.5803921568627451, + 1.0, + 0.7725490196, + 0.0, + 0.5843137254901961, + 1.0, + 0.7843137255, + 0.0, + 0.5882352941176471, + 1.0, + 0.7960784314, + 0.0, + 0.592156862745098, + 1.0, + 0.8078431373, + 0.0, + 0.596078431372549, + 1.0, + 0.8196078431, + 0.0, + 0.6, + 1.0, + 0.831372549, + 0.0, + 0.6039215686274509, + 1.0, + 0.8470588235, + 0.0, + 0.6078431372549019, + 1.0, + 0.8588235294, + 0.0, + 0.611764705882353, + 1.0, + 0.8745098039, + 0.0, + 0.615686274509804, + 1.0, + 0.8862745098, + 0.0, + 0.6196078431372549, + 1.0, + 0.8980392157, + 0.0, + 0.6235294117647059, + 1.0, + 0.9098039216, + 0.0, + 0.6274509803921569, + 1.0, + 0.9254901961, + 0.0, + 0.6313725490196078, + 1.0, + 0.937254902, + 0.0, + 0.6352941176470588, + 1.0, + 0.9490196078, + 0.0, + 0.6392156862745098, + 1.0, + 0.9607843137, + 0.0, + 0.6431372549019608, + 1.0, + 0.9764705882, + 0.0, + 0.6470588235294118, + 1.0, + 0.9803921569, + 0.0039215686, + 0.6509803921568628, + 1.0, + 0.9882352941, + 0.0117647059, + 0.6549019607843137, + 1.0, + 0.9921568627, + 0.0156862745, + 0.6588235294117647, + 1.0, + 1.0, + 0.0235294118, + 0.6627450980392157, + 1.0, + 1.0, + 0.0352941176, + 0.6666666666666666, + 1.0, + 1.0, + 0.0470588235, + 0.6705882352941176, + 1.0, + 1.0, + 0.0588235294, + 0.6745098039215687, + 1.0, + 1.0, + 0.0745098039, + 0.6784313725490196, + 1.0, + 1.0, + 0.0862745098, + 0.6823529411764706, + 1.0, + 1.0, + 0.0980392157, + 0.6862745098039216, + 1.0, + 1.0, + 0.1098039216, + 0.6901960784313725, + 1.0, + 1.0, + 0.1254901961, + 0.6941176470588235, + 1.0, + 1.0, + 0.137254902, + 0.6980392156862745, + 1.0, + 1.0, + 0.1490196078, + 0.7019607843137254, + 1.0, + 1.0, + 0.1607843137, + 0.7058823529411765, + 1.0, + 1.0, + 0.1764705882, + 0.7098039215686275, + 1.0, + 1.0, + 0.1882352941, + 0.7137254901960784, + 1.0, + 1.0, + 0.2, + 0.7176470588235294, + 1.0, + 1.0, + 0.2117647059, + 0.7215686274509804, + 1.0, + 1.0, + 0.2274509804, + 0.7254901960784313, + 1.0, + 1.0, + 0.2392156863, + 0.7294117647058823, + 1.0, + 1.0, + 0.2509803922, + 0.7333333333333333, + 1.0, + 1.0, + 0.262745098, + 0.7372549019607844, + 1.0, + 1.0, + 0.2784313725, + 0.7411764705882353, + 1.0, + 1.0, + 0.2901960784, + 0.7450980392156863, + 1.0, + 1.0, + 0.3019607843, + 0.7490196078431373, + 1.0, + 1.0, + 0.3137254902, + 0.7529411764705882, + 1.0, + 1.0, + 0.3294117647, + 0.7568627450980392, + 1.0, + 1.0, + 0.3411764706, + 0.7607843137254902, + 1.0, + 1.0, + 0.3529411765, + 0.7647058823529411, + 1.0, + 1.0, + 0.3647058824, + 0.7686274509803922, + 1.0, + 1.0, + 0.3803921569, + 0.7725490196078432, + 1.0, + 1.0, + 0.3921568627, + 0.7764705882352941, + 1.0, + 1.0, + 0.4039215686, + 0.7803921568627451, + 1.0, + 1.0, + 0.4156862745, + 0.7843137254901961, + 1.0, + 1.0, + 0.431372549, + 0.788235294117647, + 1.0, + 1.0, + 0.4431372549, + 0.792156862745098, + 1.0, + 1.0, + 0.4549019608, + 0.796078431372549, + 1.0, + 1.0, + 0.4666666667, + 0.8, + 1.0, + 1.0, + 0.4784313725, + 0.803921568627451, + 1.0, + 1.0, + 0.4901960784, + 0.807843137254902, + 1.0, + 1.0, + 0.5019607843, + 0.8117647058823529, + 1.0, + 1.0, + 0.5137254902, + 0.8156862745098039, + 1.0, + 1.0, + 0.5294117647, + 0.8196078431372549, + 1.0, + 1.0, + 0.5411764706, + 0.8235294117647058, + 1.0, + 1.0, + 0.5568627451, + 0.8274509803921568, + 1.0, + 1.0, + 0.568627451, + 0.8313725490196079, + 1.0, + 1.0, + 0.5843137255, + 0.8352941176470589, + 1.0, + 1.0, + 0.5960784314, + 0.8392156862745098, + 1.0, + 1.0, + 0.6078431373, + 0.8431372549019608, + 1.0, + 1.0, + 0.6196078431, + 0.8470588235294118, + 1.0, + 1.0, + 0.631372549, + 0.8509803921568627, + 1.0, + 1.0, + 0.6431372549, + 0.8549019607843137, + 1.0, + 1.0, + 0.6549019608, + 0.8588235294117647, + 1.0, + 1.0, + 0.6666666667, + 0.8627450980392157, + 1.0, + 1.0, + 0.6823529412, + 0.8666666666666667, + 1.0, + 1.0, + 0.6941176471, + 0.8705882352941177, + 1.0, + 1.0, + 0.7058823529, + 0.8745098039215686, + 1.0, + 1.0, + 0.7176470588, + 0.8784313725490196, + 1.0, + 1.0, + 0.7333333333, + 0.8823529411764706, + 1.0, + 1.0, + 0.7450980392, + 0.8862745098039215, + 1.0, + 1.0, + 0.7568627451, + 0.8901960784313725, + 1.0, + 1.0, + 0.768627451, + 0.8941176470588236, + 1.0, + 1.0, + 0.7843137255, + 0.8980392156862745, + 1.0, + 1.0, + 0.7960784314, + 0.9019607843137255, + 1.0, + 1.0, + 0.8078431373, + 0.9058823529411765, + 1.0, + 1.0, + 0.8196078431, + 0.9098039215686274, + 1.0, + 1.0, + 0.8352941176, + 0.9137254901960784, + 1.0, + 1.0, + 0.8470588235, + 0.9176470588235294, + 1.0, + 1.0, + 0.8588235294, + 0.9215686274509803, + 1.0, + 1.0, + 0.8705882353, + 0.9254901960784314, + 1.0, + 1.0, + 0.8823529412, + 0.9294117647058824, + 1.0, + 1.0, + 0.8941176471, + 0.9333333333333333, + 1.0, + 1.0, + 0.9098039216, + 0.9372549019607843, + 1.0, + 1.0, + 0.9215686275, + 0.9411764705882354, + 1.0, + 1.0, + 0.937254902, + 0.9450980392156864, + 1.0, + 1.0, + 0.9490196078, + 0.9490196078431372, + 1.0, + 1.0, + 0.9607843137, + 0.9529411764705882, + 1.0, + 1.0, + 0.9725490196, + 0.9568627450980394, + 1.0, + 1.0, + 0.9882352941, + 0.9607843137254903, + 1.0, + 1.0, + 0.9882352941, + 0.9647058823529413, + 1.0, + 1.0, + 0.9921568627, + 0.9686274509803922, + 1.0, + 1.0, + 0.9960784314, + 0.9725490196078431, + 1.0, + 1.0, + 1.0, + 0.9764705882352941, + 1.0, + 1.0, + 1.0, + 0.9803921568627451, + 1.0, + 1.0, + 1.0, + 0.984313725490196, + 1.0, + 1.0, + 1.0, + 0.9882352941176471, + 1.0, + 1.0, + 1.0, + 0.9921568627450981, + 1.0, + 1.0, + 1.0, + 0.996078431372549, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + ], + }, + { + ColorSpace: 'RGB', + Name: 's_pet', + RGBPoints: [ + 0.0, + 0.0156862745, + 0.0039215686, + 0.0156862745, + 0.00392156862745098, + 0.0156862745, + 0.0039215686, + 0.0156862745, + 0.00784313725490196, + 0.0274509804, + 0.0039215686, + 0.031372549, + 0.011764705882352941, + 0.0352941176, + 0.0039215686, + 0.0509803922, + 0.01568627450980392, + 0.0392156863, + 0.0039215686, + 0.0666666667, + 0.0196078431372549, + 0.0509803922, + 0.0039215686, + 0.0823529412, + 0.023529411764705882, + 0.062745098, + 0.0039215686, + 0.0980392157, + 0.027450980392156862, + 0.0705882353, + 0.0039215686, + 0.1176470588, + 0.03137254901960784, + 0.0745098039, + 0.0039215686, + 0.1333333333, + 0.03529411764705882, + 0.0862745098, + 0.0039215686, + 0.1490196078, + 0.0392156862745098, + 0.0980392157, + 0.0039215686, + 0.1647058824, + 0.043137254901960784, + 0.1058823529, + 0.0039215686, + 0.1843137255, + 0.047058823529411764, + 0.1098039216, + 0.0039215686, + 0.2, + 0.050980392156862744, + 0.1215686275, + 0.0039215686, + 0.2156862745, + 0.054901960784313725, + 0.1333333333, + 0.0039215686, + 0.231372549, + 0.05882352941176471, + 0.137254902, + 0.0039215686, + 0.2509803922, + 0.06274509803921569, + 0.1490196078, + 0.0039215686, + 0.262745098, + 0.06666666666666667, + 0.1607843137, + 0.0039215686, + 0.2784313725, + 0.07058823529411765, + 0.168627451, + 0.0039215686, + 0.2941176471, + 0.07450980392156863, + 0.1725490196, + 0.0039215686, + 0.3137254902, + 0.0784313725490196, + 0.1843137255, + 0.0039215686, + 0.3294117647, + 0.08235294117647059, + 0.1960784314, + 0.0039215686, + 0.3450980392, + 0.08627450980392157, + 0.2039215686, + 0.0039215686, + 0.3607843137, + 0.09019607843137255, + 0.2078431373, + 0.0039215686, + 0.3803921569, + 0.09411764705882353, + 0.2196078431, + 0.0039215686, + 0.3960784314, + 0.09803921568627451, + 0.231372549, + 0.0039215686, + 0.4117647059, + 0.10196078431372549, + 0.2392156863, + 0.0039215686, + 0.4274509804, + 0.10588235294117647, + 0.2431372549, + 0.0039215686, + 0.4470588235, + 0.10980392156862745, + 0.2509803922, + 0.0039215686, + 0.462745098, + 0.11372549019607843, + 0.262745098, + 0.0039215686, + 0.4784313725, + 0.11764705882352942, + 0.2666666667, + 0.0039215686, + 0.4980392157, + 0.12156862745098039, + 0.2666666667, + 0.0039215686, + 0.4980392157, + 0.12549019607843137, + 0.262745098, + 0.0039215686, + 0.5137254902, + 0.12941176470588237, + 0.2509803922, + 0.0039215686, + 0.5294117647, + 0.13333333333333333, + 0.2431372549, + 0.0039215686, + 0.5450980392, + 0.13725490196078433, + 0.2392156863, + 0.0039215686, + 0.5607843137, + 0.1411764705882353, + 0.231372549, + 0.0039215686, + 0.5764705882, + 0.1450980392156863, + 0.2196078431, + 0.0039215686, + 0.5921568627, + 0.14901960784313725, + 0.2078431373, + 0.0039215686, + 0.6078431373, + 0.15294117647058825, + 0.2039215686, + 0.0039215686, + 0.6235294118, + 0.1568627450980392, + 0.1960784314, + 0.0039215686, + 0.6392156863, + 0.1607843137254902, + 0.1843137255, + 0.0039215686, + 0.6549019608, + 0.16470588235294117, + 0.1725490196, + 0.0039215686, + 0.6705882353, + 0.16862745098039217, + 0.168627451, + 0.0039215686, + 0.6862745098, + 0.17254901960784313, + 0.1607843137, + 0.0039215686, + 0.7019607843, + 0.17647058823529413, + 0.1490196078, + 0.0039215686, + 0.7176470588, + 0.1803921568627451, + 0.137254902, + 0.0039215686, + 0.7333333333, + 0.1843137254901961, + 0.1333333333, + 0.0039215686, + 0.7490196078, + 0.18823529411764706, + 0.1215686275, + 0.0039215686, + 0.7607843137, + 0.19215686274509805, + 0.1098039216, + 0.0039215686, + 0.7764705882, + 0.19607843137254902, + 0.1058823529, + 0.0039215686, + 0.7921568627, + 0.2, + 0.0980392157, + 0.0039215686, + 0.8078431373, + 0.20392156862745098, + 0.0862745098, + 0.0039215686, + 0.8235294118, + 0.20784313725490197, + 0.0745098039, + 0.0039215686, + 0.8392156863, + 0.21176470588235294, + 0.0705882353, + 0.0039215686, + 0.8549019608, + 0.21568627450980393, + 0.062745098, + 0.0039215686, + 0.8705882353, + 0.2196078431372549, + 0.0509803922, + 0.0039215686, + 0.8862745098, + 0.2235294117647059, + 0.0392156863, + 0.0039215686, + 0.9019607843, + 0.22745098039215686, + 0.0352941176, + 0.0039215686, + 0.9176470588, + 0.23137254901960785, + 0.0274509804, + 0.0039215686, + 0.9333333333, + 0.23529411764705885, + 0.0156862745, + 0.0039215686, + 0.9490196078, + 0.23921568627450984, + 0.0078431373, + 0.0039215686, + 0.9647058824, + 0.24313725490196078, + 0.0039215686, + 0.0039215686, + 0.9960784314, + 0.24705882352941178, + 0.0039215686, + 0.0039215686, + 0.9960784314, + 0.25098039215686274, + 0.0039215686, + 0.0196078431, + 0.9647058824, + 0.2549019607843137, + 0.0039215686, + 0.0392156863, + 0.9490196078, + 0.25882352941176473, + 0.0039215686, + 0.0549019608, + 0.9333333333, + 0.2627450980392157, + 0.0039215686, + 0.0745098039, + 0.9176470588, + 0.26666666666666666, + 0.0039215686, + 0.0901960784, + 0.9019607843, + 0.27058823529411763, + 0.0039215686, + 0.1098039216, + 0.8862745098, + 0.27450980392156865, + 0.0039215686, + 0.1254901961, + 0.8705882353, + 0.2784313725490196, + 0.0039215686, + 0.1450980392, + 0.8549019608, + 0.2823529411764706, + 0.0039215686, + 0.1607843137, + 0.8392156863, + 0.28627450980392155, + 0.0039215686, + 0.1803921569, + 0.8235294118, + 0.2901960784313726, + 0.0039215686, + 0.1960784314, + 0.8078431373, + 0.29411764705882354, + 0.0039215686, + 0.2156862745, + 0.7921568627, + 0.2980392156862745, + 0.0039215686, + 0.231372549, + 0.7764705882, + 0.30196078431372547, + 0.0039215686, + 0.2509803922, + 0.7607843137, + 0.3058823529411765, + 0.0039215686, + 0.262745098, + 0.7490196078, + 0.30980392156862746, + 0.0039215686, + 0.2823529412, + 0.7333333333, + 0.3137254901960784, + 0.0039215686, + 0.2980392157, + 0.7176470588, + 0.3176470588235294, + 0.0039215686, + 0.3176470588, + 0.7019607843, + 0.3215686274509804, + 0.0039215686, + 0.3333333333, + 0.6862745098, + 0.3254901960784314, + 0.0039215686, + 0.3529411765, + 0.6705882353, + 0.32941176470588235, + 0.0039215686, + 0.368627451, + 0.6549019608, + 0.3333333333333333, + 0.0039215686, + 0.3882352941, + 0.6392156863, + 0.33725490196078434, + 0.0039215686, + 0.4039215686, + 0.6235294118, + 0.3411764705882353, + 0.0039215686, + 0.4235294118, + 0.6078431373, + 0.34509803921568627, + 0.0039215686, + 0.4392156863, + 0.5921568627, + 0.34901960784313724, + 0.0039215686, + 0.4588235294, + 0.5764705882, + 0.35294117647058826, + 0.0039215686, + 0.4745098039, + 0.5607843137, + 0.3568627450980392, + 0.0039215686, + 0.4941176471, + 0.5450980392, + 0.3607843137254902, + 0.0039215686, + 0.5098039216, + 0.5294117647, + 0.36470588235294116, + 0.0039215686, + 0.5294117647, + 0.5137254902, + 0.3686274509803922, + 0.0039215686, + 0.5450980392, + 0.4980392157, + 0.37254901960784315, + 0.0039215686, + 0.5647058824, + 0.4784313725, + 0.3764705882352941, + 0.0039215686, + 0.5803921569, + 0.462745098, + 0.3803921568627451, + 0.0039215686, + 0.6, + 0.4470588235, + 0.3843137254901961, + 0.0039215686, + 0.6156862745, + 0.4274509804, + 0.38823529411764707, + 0.0039215686, + 0.6352941176, + 0.4117647059, + 0.39215686274509803, + 0.0039215686, + 0.6509803922, + 0.3960784314, + 0.396078431372549, + 0.0039215686, + 0.6705882353, + 0.3803921569, + 0.4, + 0.0039215686, + 0.6862745098, + 0.3607843137, + 0.403921568627451, + 0.0039215686, + 0.7058823529, + 0.3450980392, + 0.40784313725490196, + 0.0039215686, + 0.7215686275, + 0.3294117647, + 0.4117647058823529, + 0.0039215686, + 0.7411764706, + 0.3137254902, + 0.41568627450980394, + 0.0039215686, + 0.7529411765, + 0.2941176471, + 0.4196078431372549, + 0.0039215686, + 0.7960784314, + 0.2784313725, + 0.4235294117647059, + 0.0039215686, + 0.7960784314, + 0.262745098, + 0.42745098039215684, + 0.0392156863, + 0.8039215686, + 0.2509803922, + 0.43137254901960786, + 0.0745098039, + 0.8117647059, + 0.231372549, + 0.43529411764705883, + 0.1098039216, + 0.8196078431, + 0.2156862745, + 0.4392156862745098, + 0.1450980392, + 0.8274509804, + 0.2, + 0.44313725490196076, + 0.1803921569, + 0.8352941176, + 0.1843137255, + 0.4470588235294118, + 0.2156862745, + 0.8431372549, + 0.1647058824, + 0.45098039215686275, + 0.2509803922, + 0.8509803922, + 0.1490196078, + 0.4549019607843137, + 0.2823529412, + 0.8588235294, + 0.1333333333, + 0.4588235294117647, + 0.3176470588, + 0.8666666667, + 0.1176470588, + 0.4627450980392157, + 0.3529411765, + 0.8745098039, + 0.0980392157, + 0.4666666666666667, + 0.3882352941, + 0.8823529412, + 0.0823529412, + 0.4705882352941177, + 0.4235294118, + 0.8901960784, + 0.0666666667, + 0.4745098039215686, + 0.4588235294, + 0.8980392157, + 0.0509803922, + 0.4784313725490197, + 0.4941176471, + 0.9058823529, + 0.0431372549, + 0.48235294117647065, + 0.5294117647, + 0.9137254902, + 0.031372549, + 0.48627450980392156, + 0.5647058824, + 0.9215686275, + 0.0196078431, + 0.49019607843137253, + 0.6, + 0.9294117647, + 0.0078431373, + 0.49411764705882355, + 0.6352941176, + 0.937254902, + 0.0039215686, + 0.4980392156862745, + 0.6705882353, + 0.9450980392, + 0.0039215686, + 0.5019607843137255, + 0.7058823529, + 0.9490196078, + 0.0039215686, + 0.5058823529411764, + 0.7411764706, + 0.9568627451, + 0.0039215686, + 0.5098039215686274, + 0.7725490196, + 0.9607843137, + 0.0039215686, + 0.5137254901960784, + 0.8078431373, + 0.968627451, + 0.0039215686, + 0.5176470588235295, + 0.8431372549, + 0.9725490196, + 0.0039215686, + 0.5215686274509804, + 0.8784313725, + 0.9803921569, + 0.0039215686, + 0.5254901960784314, + 0.9137254902, + 0.9843137255, + 0.0039215686, + 0.5294117647058824, + 0.9490196078, + 0.9921568627, + 0.0039215686, + 0.5333333333333333, + 0.9960784314, + 0.9960784314, + 0.0039215686, + 0.5372549019607843, + 0.9960784314, + 0.9960784314, + 0.0039215686, + 0.5411764705882353, + 0.9960784314, + 0.9921568627, + 0.0039215686, + 0.5450980392156862, + 0.9960784314, + 0.9843137255, + 0.0039215686, + 0.5490196078431373, + 0.9960784314, + 0.9764705882, + 0.0039215686, + 0.5529411764705883, + 0.9960784314, + 0.968627451, + 0.0039215686, + 0.5568627450980392, + 0.9960784314, + 0.9607843137, + 0.0039215686, + 0.5607843137254902, + 0.9960784314, + 0.9529411765, + 0.0039215686, + 0.5647058823529412, + 0.9960784314, + 0.9450980392, + 0.0039215686, + 0.5686274509803921, + 0.9960784314, + 0.937254902, + 0.0039215686, + 0.5725490196078431, + 0.9960784314, + 0.9294117647, + 0.0039215686, + 0.5764705882352941, + 0.9960784314, + 0.9215686275, + 0.0039215686, + 0.5803921568627451, + 0.9960784314, + 0.9137254902, + 0.0039215686, + 0.5843137254901961, + 0.9960784314, + 0.9058823529, + 0.0039215686, + 0.5882352941176471, + 0.9960784314, + 0.8980392157, + 0.0039215686, + 0.592156862745098, + 0.9960784314, + 0.8901960784, + 0.0039215686, + 0.596078431372549, + 0.9960784314, + 0.8823529412, + 0.0039215686, + 0.6, + 0.9960784314, + 0.8745098039, + 0.0039215686, + 0.6039215686274509, + 0.9960784314, + 0.8666666667, + 0.0039215686, + 0.6078431372549019, + 0.9960784314, + 0.8588235294, + 0.0039215686, + 0.611764705882353, + 0.9960784314, + 0.8509803922, + 0.0039215686, + 0.615686274509804, + 0.9960784314, + 0.8431372549, + 0.0039215686, + 0.6196078431372549, + 0.9960784314, + 0.8352941176, + 0.0039215686, + 0.6235294117647059, + 0.9960784314, + 0.8274509804, + 0.0039215686, + 0.6274509803921569, + 0.9960784314, + 0.8196078431, + 0.0039215686, + 0.6313725490196078, + 0.9960784314, + 0.8117647059, + 0.0039215686, + 0.6352941176470588, + 0.9960784314, + 0.8039215686, + 0.0039215686, + 0.6392156862745098, + 0.9960784314, + 0.7960784314, + 0.0039215686, + 0.6431372549019608, + 0.9960784314, + 0.7882352941, + 0.0039215686, + 0.6470588235294118, + 0.9960784314, + 0.7803921569, + 0.0039215686, + 0.6509803921568628, + 0.9960784314, + 0.7725490196, + 0.0039215686, + 0.6549019607843137, + 0.9960784314, + 0.7647058824, + 0.0039215686, + 0.6588235294117647, + 0.9960784314, + 0.7568627451, + 0.0039215686, + 0.6627450980392157, + 0.9960784314, + 0.7490196078, + 0.0039215686, + 0.6666666666666666, + 0.9960784314, + 0.7450980392, + 0.0039215686, + 0.6705882352941176, + 0.9960784314, + 0.737254902, + 0.0039215686, + 0.6745098039215687, + 0.9960784314, + 0.7294117647, + 0.0039215686, + 0.6784313725490196, + 0.9960784314, + 0.7215686275, + 0.0039215686, + 0.6823529411764706, + 0.9960784314, + 0.7137254902, + 0.0039215686, + 0.6862745098039216, + 0.9960784314, + 0.7058823529, + 0.0039215686, + 0.6901960784313725, + 0.9960784314, + 0.6980392157, + 0.0039215686, + 0.6941176470588235, + 0.9960784314, + 0.6901960784, + 0.0039215686, + 0.6980392156862745, + 0.9960784314, + 0.6823529412, + 0.0039215686, + 0.7019607843137254, + 0.9960784314, + 0.6745098039, + 0.0039215686, + 0.7058823529411765, + 0.9960784314, + 0.6666666667, + 0.0039215686, + 0.7098039215686275, + 0.9960784314, + 0.6588235294, + 0.0039215686, + 0.7137254901960784, + 0.9960784314, + 0.6509803922, + 0.0039215686, + 0.7176470588235294, + 0.9960784314, + 0.6431372549, + 0.0039215686, + 0.7215686274509804, + 0.9960784314, + 0.6352941176, + 0.0039215686, + 0.7254901960784313, + 0.9960784314, + 0.6274509804, + 0.0039215686, + 0.7294117647058823, + 0.9960784314, + 0.6196078431, + 0.0039215686, + 0.7333333333333333, + 0.9960784314, + 0.6117647059, + 0.0039215686, + 0.7372549019607844, + 0.9960784314, + 0.6039215686, + 0.0039215686, + 0.7411764705882353, + 0.9960784314, + 0.5960784314, + 0.0039215686, + 0.7450980392156863, + 0.9960784314, + 0.5882352941, + 0.0039215686, + 0.7490196078431373, + 0.9960784314, + 0.5803921569, + 0.0039215686, + 0.7529411764705882, + 0.9960784314, + 0.5725490196, + 0.0039215686, + 0.7568627450980392, + 0.9960784314, + 0.5647058824, + 0.0039215686, + 0.7607843137254902, + 0.9960784314, + 0.5568627451, + 0.0039215686, + 0.7647058823529411, + 0.9960784314, + 0.5490196078, + 0.0039215686, + 0.7686274509803922, + 0.9960784314, + 0.5411764706, + 0.0039215686, + 0.7725490196078432, + 0.9960784314, + 0.5333333333, + 0.0039215686, + 0.7764705882352941, + 0.9960784314, + 0.5254901961, + 0.0039215686, + 0.7803921568627451, + 0.9960784314, + 0.5176470588, + 0.0039215686, + 0.7843137254901961, + 0.9960784314, + 0.5098039216, + 0.0039215686, + 0.788235294117647, + 0.9960784314, + 0.5019607843, + 0.0039215686, + 0.792156862745098, + 0.9960784314, + 0.4941176471, + 0.0039215686, + 0.796078431372549, + 0.9960784314, + 0.4862745098, + 0.0039215686, + 0.8, + 0.9960784314, + 0.4784313725, + 0.0039215686, + 0.803921568627451, + 0.9960784314, + 0.4705882353, + 0.0039215686, + 0.807843137254902, + 0.9960784314, + 0.462745098, + 0.0039215686, + 0.8117647058823529, + 0.9960784314, + 0.4549019608, + 0.0039215686, + 0.8156862745098039, + 0.9960784314, + 0.4470588235, + 0.0039215686, + 0.8196078431372549, + 0.9960784314, + 0.4392156863, + 0.0039215686, + 0.8235294117647058, + 0.9960784314, + 0.431372549, + 0.0039215686, + 0.8274509803921568, + 0.9960784314, + 0.4235294118, + 0.0039215686, + 0.8313725490196079, + 0.9960784314, + 0.4156862745, + 0.0039215686, + 0.8352941176470589, + 0.9960784314, + 0.4078431373, + 0.0039215686, + 0.8392156862745098, + 0.9960784314, + 0.4, + 0.0039215686, + 0.8431372549019608, + 0.9960784314, + 0.3921568627, + 0.0039215686, + 0.8470588235294118, + 0.9960784314, + 0.3843137255, + 0.0039215686, + 0.8509803921568627, + 0.9960784314, + 0.3764705882, + 0.0039215686, + 0.8549019607843137, + 0.9960784314, + 0.368627451, + 0.0039215686, + 0.8588235294117647, + 0.9960784314, + 0.3607843137, + 0.0039215686, + 0.8627450980392157, + 0.9960784314, + 0.3529411765, + 0.0039215686, + 0.8666666666666667, + 0.9960784314, + 0.3450980392, + 0.0039215686, + 0.8705882352941177, + 0.9960784314, + 0.337254902, + 0.0039215686, + 0.8745098039215686, + 0.9960784314, + 0.3294117647, + 0.0039215686, + 0.8784313725490196, + 0.9960784314, + 0.3215686275, + 0.0039215686, + 0.8823529411764706, + 0.9960784314, + 0.3137254902, + 0.0039215686, + 0.8862745098039215, + 0.9960784314, + 0.3058823529, + 0.0039215686, + 0.8901960784313725, + 0.9960784314, + 0.2980392157, + 0.0039215686, + 0.8941176470588236, + 0.9960784314, + 0.2901960784, + 0.0039215686, + 0.8980392156862745, + 0.9960784314, + 0.2823529412, + 0.0039215686, + 0.9019607843137255, + 0.9960784314, + 0.2705882353, + 0.0039215686, + 0.9058823529411765, + 0.9960784314, + 0.2588235294, + 0.0039215686, + 0.9098039215686274, + 0.9960784314, + 0.2509803922, + 0.0039215686, + 0.9137254901960784, + 0.9960784314, + 0.2431372549, + 0.0039215686, + 0.9176470588235294, + 0.9960784314, + 0.231372549, + 0.0039215686, + 0.9215686274509803, + 0.9960784314, + 0.2196078431, + 0.0039215686, + 0.9254901960784314, + 0.9960784314, + 0.2117647059, + 0.0039215686, + 0.9294117647058824, + 0.9960784314, + 0.2, + 0.0039215686, + 0.9333333333333333, + 0.9960784314, + 0.1882352941, + 0.0039215686, + 0.9372549019607843, + 0.9960784314, + 0.1764705882, + 0.0039215686, + 0.9411764705882354, + 0.9960784314, + 0.168627451, + 0.0039215686, + 0.9450980392156864, + 0.9960784314, + 0.1568627451, + 0.0039215686, + 0.9490196078431372, + 0.9960784314, + 0.1450980392, + 0.0039215686, + 0.9529411764705882, + 0.9960784314, + 0.1333333333, + 0.0039215686, + 0.9568627450980394, + 0.9960784314, + 0.1254901961, + 0.0039215686, + 0.9607843137254903, + 0.9960784314, + 0.1137254902, + 0.0039215686, + 0.9647058823529413, + 0.9960784314, + 0.1019607843, + 0.0039215686, + 0.9686274509803922, + 0.9960784314, + 0.0901960784, + 0.0039215686, + 0.9725490196078431, + 0.9960784314, + 0.0823529412, + 0.0039215686, + 0.9764705882352941, + 0.9960784314, + 0.0705882353, + 0.0039215686, + 0.9803921568627451, + 0.9960784314, + 0.0588235294, + 0.0039215686, + 0.984313725490196, + 0.9960784314, + 0.0470588235, + 0.0039215686, + 0.9882352941176471, + 0.9960784314, + 0.0392156863, + 0.0039215686, + 0.9921568627450981, + 0.9960784314, + 0.0274509804, + 0.0039215686, + 0.996078431372549, + 0.9960784314, + 0.0156862745, + 0.0039215686, + 1.0, + 0.9960784314, + 0.0156862745, + 0.0039215686, + ], + }, + { + ColorSpace: 'RGB', + Name: 'perfusion', + RGBPoints: [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.00392156862745098, + 0.0078431373, + 0.0235294118, + 0.0235294118, + 0.00784313725490196, + 0.0078431373, + 0.031372549, + 0.0470588235, + 0.011764705882352941, + 0.0078431373, + 0.0392156863, + 0.062745098, + 0.01568627450980392, + 0.0078431373, + 0.0470588235, + 0.0862745098, + 0.0196078431372549, + 0.0078431373, + 0.0549019608, + 0.1019607843, + 0.023529411764705882, + 0.0078431373, + 0.0549019608, + 0.1254901961, + 0.027450980392156862, + 0.0078431373, + 0.062745098, + 0.1411764706, + 0.03137254901960784, + 0.0078431373, + 0.0705882353, + 0.1647058824, + 0.03529411764705882, + 0.0078431373, + 0.0784313725, + 0.1803921569, + 0.0392156862745098, + 0.0078431373, + 0.0862745098, + 0.2039215686, + 0.043137254901960784, + 0.0078431373, + 0.0862745098, + 0.2196078431, + 0.047058823529411764, + 0.0078431373, + 0.0941176471, + 0.2431372549, + 0.050980392156862744, + 0.0078431373, + 0.1019607843, + 0.2666666667, + 0.054901960784313725, + 0.0078431373, + 0.1098039216, + 0.2823529412, + 0.05882352941176471, + 0.0078431373, + 0.1176470588, + 0.3058823529, + 0.06274509803921569, + 0.0078431373, + 0.1176470588, + 0.3215686275, + 0.06666666666666667, + 0.0078431373, + 0.1254901961, + 0.3450980392, + 0.07058823529411765, + 0.0078431373, + 0.1333333333, + 0.3607843137, + 0.07450980392156863, + 0.0078431373, + 0.1411764706, + 0.3843137255, + 0.0784313725490196, + 0.0078431373, + 0.1490196078, + 0.4, + 0.08235294117647059, + 0.0078431373, + 0.1490196078, + 0.4235294118, + 0.08627450980392157, + 0.0078431373, + 0.1568627451, + 0.4392156863, + 0.09019607843137255, + 0.0078431373, + 0.1647058824, + 0.462745098, + 0.09411764705882353, + 0.0078431373, + 0.1725490196, + 0.4784313725, + 0.09803921568627451, + 0.0078431373, + 0.1803921569, + 0.5019607843, + 0.10196078431372549, + 0.0078431373, + 0.1803921569, + 0.5254901961, + 0.10588235294117647, + 0.0078431373, + 0.1882352941, + 0.5411764706, + 0.10980392156862745, + 0.0078431373, + 0.1960784314, + 0.5647058824, + 0.11372549019607843, + 0.0078431373, + 0.2039215686, + 0.5803921569, + 0.11764705882352942, + 0.0078431373, + 0.2117647059, + 0.6039215686, + 0.12156862745098039, + 0.0078431373, + 0.2117647059, + 0.6196078431, + 0.12549019607843137, + 0.0078431373, + 0.2196078431, + 0.6431372549, + 0.12941176470588237, + 0.0078431373, + 0.2274509804, + 0.6588235294, + 0.13333333333333333, + 0.0078431373, + 0.2352941176, + 0.6823529412, + 0.13725490196078433, + 0.0078431373, + 0.2431372549, + 0.6980392157, + 0.1411764705882353, + 0.0078431373, + 0.2431372549, + 0.7215686275, + 0.1450980392156863, + 0.0078431373, + 0.2509803922, + 0.737254902, + 0.14901960784313725, + 0.0078431373, + 0.2588235294, + 0.7607843137, + 0.15294117647058825, + 0.0078431373, + 0.2666666667, + 0.7843137255, + 0.1568627450980392, + 0.0078431373, + 0.2745098039, + 0.8, + 0.1607843137254902, + 0.0078431373, + 0.2745098039, + 0.8235294118, + 0.16470588235294117, + 0.0078431373, + 0.2823529412, + 0.8392156863, + 0.16862745098039217, + 0.0078431373, + 0.2901960784, + 0.862745098, + 0.17254901960784313, + 0.0078431373, + 0.2980392157, + 0.8784313725, + 0.17647058823529413, + 0.0078431373, + 0.3058823529, + 0.9019607843, + 0.1803921568627451, + 0.0078431373, + 0.3058823529, + 0.9176470588, + 0.1843137254901961, + 0.0078431373, + 0.2980392157, + 0.9411764706, + 0.18823529411764706, + 0.0078431373, + 0.3058823529, + 0.9568627451, + 0.19215686274509805, + 0.0078431373, + 0.2980392157, + 0.9803921569, + 0.19607843137254902, + 0.0078431373, + 0.2980392157, + 0.9882352941, + 0.2, + 0.0078431373, + 0.2901960784, + 0.9803921569, + 0.20392156862745098, + 0.0078431373, + 0.2901960784, + 0.9647058824, + 0.20784313725490197, + 0.0078431373, + 0.2823529412, + 0.9568627451, + 0.21176470588235294, + 0.0078431373, + 0.2823529412, + 0.9411764706, + 0.21568627450980393, + 0.0078431373, + 0.2745098039, + 0.9333333333, + 0.2196078431372549, + 0.0078431373, + 0.2666666667, + 0.9176470588, + 0.2235294117647059, + 0.0078431373, + 0.2666666667, + 0.9098039216, + 0.22745098039215686, + 0.0078431373, + 0.2588235294, + 0.9019607843, + 0.23137254901960785, + 0.0078431373, + 0.2588235294, + 0.8862745098, + 0.23529411764705885, + 0.0078431373, + 0.2509803922, + 0.8784313725, + 0.23921568627450984, + 0.0078431373, + 0.2509803922, + 0.862745098, + 0.24313725490196078, + 0.0078431373, + 0.2431372549, + 0.8549019608, + 0.24705882352941178, + 0.0078431373, + 0.2352941176, + 0.8392156863, + 0.25098039215686274, + 0.0078431373, + 0.2352941176, + 0.831372549, + 0.2549019607843137, + 0.0078431373, + 0.2274509804, + 0.8235294118, + 0.25882352941176473, + 0.0078431373, + 0.2274509804, + 0.8078431373, + 0.2627450980392157, + 0.0078431373, + 0.2196078431, + 0.8, + 0.26666666666666666, + 0.0078431373, + 0.2196078431, + 0.7843137255, + 0.27058823529411763, + 0.0078431373, + 0.2117647059, + 0.7764705882, + 0.27450980392156865, + 0.0078431373, + 0.2039215686, + 0.7607843137, + 0.2784313725490196, + 0.0078431373, + 0.2039215686, + 0.7529411765, + 0.2823529411764706, + 0.0078431373, + 0.1960784314, + 0.7450980392, + 0.28627450980392155, + 0.0078431373, + 0.1960784314, + 0.7294117647, + 0.2901960784313726, + 0.0078431373, + 0.1882352941, + 0.7215686275, + 0.29411764705882354, + 0.0078431373, + 0.1882352941, + 0.7058823529, + 0.2980392156862745, + 0.0078431373, + 0.1803921569, + 0.6980392157, + 0.30196078431372547, + 0.0078431373, + 0.1803921569, + 0.6823529412, + 0.3058823529411765, + 0.0078431373, + 0.1725490196, + 0.6745098039, + 0.30980392156862746, + 0.0078431373, + 0.1647058824, + 0.6666666667, + 0.3137254901960784, + 0.0078431373, + 0.1647058824, + 0.6509803922, + 0.3176470588235294, + 0.0078431373, + 0.1568627451, + 0.6431372549, + 0.3215686274509804, + 0.0078431373, + 0.1568627451, + 0.6274509804, + 0.3254901960784314, + 0.0078431373, + 0.1490196078, + 0.6196078431, + 0.32941176470588235, + 0.0078431373, + 0.1490196078, + 0.6039215686, + 0.3333333333333333, + 0.0078431373, + 0.1411764706, + 0.5960784314, + 0.33725490196078434, + 0.0078431373, + 0.1333333333, + 0.5882352941, + 0.3411764705882353, + 0.0078431373, + 0.1333333333, + 0.5725490196, + 0.34509803921568627, + 0.0078431373, + 0.1254901961, + 0.5647058824, + 0.34901960784313724, + 0.0078431373, + 0.1254901961, + 0.5490196078, + 0.35294117647058826, + 0.0078431373, + 0.1176470588, + 0.5411764706, + 0.3568627450980392, + 0.0078431373, + 0.1176470588, + 0.5254901961, + 0.3607843137254902, + 0.0078431373, + 0.1098039216, + 0.5176470588, + 0.36470588235294116, + 0.0078431373, + 0.1019607843, + 0.5098039216, + 0.3686274509803922, + 0.0078431373, + 0.1019607843, + 0.4941176471, + 0.37254901960784315, + 0.0078431373, + 0.0941176471, + 0.4862745098, + 0.3764705882352941, + 0.0078431373, + 0.0941176471, + 0.4705882353, + 0.3803921568627451, + 0.0078431373, + 0.0862745098, + 0.462745098, + 0.3843137254901961, + 0.0078431373, + 0.0862745098, + 0.4470588235, + 0.38823529411764707, + 0.0078431373, + 0.0784313725, + 0.4392156863, + 0.39215686274509803, + 0.0078431373, + 0.0705882353, + 0.431372549, + 0.396078431372549, + 0.0078431373, + 0.0705882353, + 0.4156862745, + 0.4, + 0.0078431373, + 0.062745098, + 0.4078431373, + 0.403921568627451, + 0.0078431373, + 0.062745098, + 0.3921568627, + 0.40784313725490196, + 0.0078431373, + 0.0549019608, + 0.3843137255, + 0.4117647058823529, + 0.0078431373, + 0.0549019608, + 0.368627451, + 0.41568627450980394, + 0.0078431373, + 0.0470588235, + 0.3607843137, + 0.4196078431372549, + 0.0078431373, + 0.0470588235, + 0.3529411765, + 0.4235294117647059, + 0.0078431373, + 0.0392156863, + 0.337254902, + 0.42745098039215684, + 0.0078431373, + 0.031372549, + 0.3294117647, + 0.43137254901960786, + 0.0078431373, + 0.031372549, + 0.3137254902, + 0.43529411764705883, + 0.0078431373, + 0.0235294118, + 0.3058823529, + 0.4392156862745098, + 0.0078431373, + 0.0235294118, + 0.2901960784, + 0.44313725490196076, + 0.0078431373, + 0.0156862745, + 0.2823529412, + 0.4470588235294118, + 0.0078431373, + 0.0156862745, + 0.2745098039, + 0.45098039215686275, + 0.0078431373, + 0.0078431373, + 0.2588235294, + 0.4549019607843137, + 0.0235294118, + 0.0078431373, + 0.2509803922, + 0.4588235294117647, + 0.0078431373, + 0.0078431373, + 0.2352941176, + 0.4627450980392157, + 0.0078431373, + 0.0078431373, + 0.2274509804, + 0.4666666666666667, + 0.0078431373, + 0.0078431373, + 0.2117647059, + 0.4705882352941177, + 0.0078431373, + 0.0078431373, + 0.2039215686, + 0.4745098039215686, + 0.0078431373, + 0.0078431373, + 0.1960784314, + 0.4784313725490197, + 0.0078431373, + 0.0078431373, + 0.1803921569, + 0.48235294117647065, + 0.0078431373, + 0.0078431373, + 0.1725490196, + 0.48627450980392156, + 0.0078431373, + 0.0078431373, + 0.1568627451, + 0.49019607843137253, + 0.0078431373, + 0.0078431373, + 0.1490196078, + 0.49411764705882355, + 0.0078431373, + 0.0078431373, + 0.1333333333, + 0.4980392156862745, + 0.0078431373, + 0.0078431373, + 0.1254901961, + 0.5019607843137255, + 0.0078431373, + 0.0078431373, + 0.1176470588, + 0.5058823529411764, + 0.0078431373, + 0.0078431373, + 0.1019607843, + 0.5098039215686274, + 0.0078431373, + 0.0078431373, + 0.0941176471, + 0.5137254901960784, + 0.0078431373, + 0.0078431373, + 0.0784313725, + 0.5176470588235295, + 0.0078431373, + 0.0078431373, + 0.0705882353, + 0.5215686274509804, + 0.0078431373, + 0.0078431373, + 0.0549019608, + 0.5254901960784314, + 0.0078431373, + 0.0078431373, + 0.0470588235, + 0.5294117647058824, + 0.0235294118, + 0.0078431373, + 0.0392156863, + 0.5333333333333333, + 0.031372549, + 0.0078431373, + 0.0235294118, + 0.5372549019607843, + 0.0392156863, + 0.0078431373, + 0.0156862745, + 0.5411764705882353, + 0.0549019608, + 0.0078431373, + 0.0, + 0.5450980392156862, + 0.062745098, + 0.0078431373, + 0.0, + 0.5490196078431373, + 0.0705882353, + 0.0078431373, + 0.0, + 0.5529411764705883, + 0.0862745098, + 0.0078431373, + 0.0, + 0.5568627450980392, + 0.0941176471, + 0.0078431373, + 0.0, + 0.5607843137254902, + 0.1019607843, + 0.0078431373, + 0.0, + 0.5647058823529412, + 0.1098039216, + 0.0078431373, + 0.0, + 0.5686274509803921, + 0.1254901961, + 0.0078431373, + 0.0, + 0.5725490196078431, + 0.1333333333, + 0.0078431373, + 0.0, + 0.5764705882352941, + 0.1411764706, + 0.0078431373, + 0.0, + 0.5803921568627451, + 0.1568627451, + 0.0078431373, + 0.0, + 0.5843137254901961, + 0.1647058824, + 0.0078431373, + 0.0, + 0.5882352941176471, + 0.1725490196, + 0.0078431373, + 0.0, + 0.592156862745098, + 0.1882352941, + 0.0078431373, + 0.0, + 0.596078431372549, + 0.1960784314, + 0.0078431373, + 0.0, + 0.6, + 0.2039215686, + 0.0078431373, + 0.0, + 0.6039215686274509, + 0.2117647059, + 0.0078431373, + 0.0, + 0.6078431372549019, + 0.2274509804, + 0.0078431373, + 0.0, + 0.611764705882353, + 0.2352941176, + 0.0078431373, + 0.0, + 0.615686274509804, + 0.2431372549, + 0.0078431373, + 0.0, + 0.6196078431372549, + 0.2588235294, + 0.0078431373, + 0.0, + 0.6235294117647059, + 0.2666666667, + 0.0078431373, + 0.0, + 0.6274509803921569, + 0.2745098039, + 0.0, + 0.0, + 0.6313725490196078, + 0.2901960784, + 0.0156862745, + 0.0, + 0.6352941176470588, + 0.2980392157, + 0.0235294118, + 0.0, + 0.6392156862745098, + 0.3058823529, + 0.0392156863, + 0.0, + 0.6431372549019608, + 0.3137254902, + 0.0470588235, + 0.0, + 0.6470588235294118, + 0.3294117647, + 0.0549019608, + 0.0, + 0.6509803921568628, + 0.337254902, + 0.0705882353, + 0.0, + 0.6549019607843137, + 0.3450980392, + 0.0784313725, + 0.0, + 0.6588235294117647, + 0.3607843137, + 0.0862745098, + 0.0, + 0.6627450980392157, + 0.368627451, + 0.1019607843, + 0.0, + 0.6666666666666666, + 0.3764705882, + 0.1098039216, + 0.0, + 0.6705882352941176, + 0.3843137255, + 0.1176470588, + 0.0, + 0.6745098039215687, + 0.4, + 0.1333333333, + 0.0, + 0.6784313725490196, + 0.4078431373, + 0.1411764706, + 0.0, + 0.6823529411764706, + 0.4156862745, + 0.1490196078, + 0.0, + 0.6862745098039216, + 0.431372549, + 0.1647058824, + 0.0, + 0.6901960784313725, + 0.4392156863, + 0.1725490196, + 0.0, + 0.6941176470588235, + 0.4470588235, + 0.1803921569, + 0.0, + 0.6980392156862745, + 0.462745098, + 0.1960784314, + 0.0, + 0.7019607843137254, + 0.4705882353, + 0.2039215686, + 0.0, + 0.7058823529411765, + 0.4784313725, + 0.2117647059, + 0.0, + 0.7098039215686275, + 0.4862745098, + 0.2274509804, + 0.0, + 0.7137254901960784, + 0.5019607843, + 0.2352941176, + 0.0, + 0.7176470588235294, + 0.5098039216, + 0.2431372549, + 0.0, + 0.7215686274509804, + 0.5176470588, + 0.2588235294, + 0.0, + 0.7254901960784313, + 0.5333333333, + 0.2666666667, + 0.0, + 0.7294117647058823, + 0.5411764706, + 0.2745098039, + 0.0, + 0.7333333333333333, + 0.5490196078, + 0.2901960784, + 0.0, + 0.7372549019607844, + 0.5647058824, + 0.2980392157, + 0.0, + 0.7411764705882353, + 0.5725490196, + 0.3058823529, + 0.0, + 0.7450980392156863, + 0.5803921569, + 0.3215686275, + 0.0, + 0.7490196078431373, + 0.5882352941, + 0.3294117647, + 0.0, + 0.7529411764705882, + 0.6039215686, + 0.337254902, + 0.0, + 0.7568627450980392, + 0.6117647059, + 0.3529411765, + 0.0, + 0.7607843137254902, + 0.6196078431, + 0.3607843137, + 0.0, + 0.7647058823529411, + 0.6352941176, + 0.368627451, + 0.0, + 0.7686274509803922, + 0.6431372549, + 0.3843137255, + 0.0, + 0.7725490196078432, + 0.6509803922, + 0.3921568627, + 0.0, + 0.7764705882352941, + 0.6588235294, + 0.4, + 0.0, + 0.7803921568627451, + 0.6745098039, + 0.4156862745, + 0.0, + 0.7843137254901961, + 0.6823529412, + 0.4235294118, + 0.0, + 0.788235294117647, + 0.6901960784, + 0.431372549, + 0.0, + 0.792156862745098, + 0.7058823529, + 0.4470588235, + 0.0, + 0.796078431372549, + 0.7137254902, + 0.4549019608, + 0.0, + 0.8, + 0.7215686275, + 0.462745098, + 0.0, + 0.803921568627451, + 0.737254902, + 0.4784313725, + 0.0, + 0.807843137254902, + 0.7450980392, + 0.4862745098, + 0.0, + 0.8117647058823529, + 0.7529411765, + 0.4941176471, + 0.0, + 0.8156862745098039, + 0.7607843137, + 0.5098039216, + 0.0, + 0.8196078431372549, + 0.7764705882, + 0.5176470588, + 0.0, + 0.8235294117647058, + 0.7843137255, + 0.5254901961, + 0.0, + 0.8274509803921568, + 0.7921568627, + 0.5411764706, + 0.0, + 0.8313725490196079, + 0.8078431373, + 0.5490196078, + 0.0, + 0.8352941176470589, + 0.8156862745, + 0.5568627451, + 0.0, + 0.8392156862745098, + 0.8235294118, + 0.5725490196, + 0.0, + 0.8431372549019608, + 0.8392156863, + 0.5803921569, + 0.0, + 0.8470588235294118, + 0.8470588235, + 0.5882352941, + 0.0, + 0.8509803921568627, + 0.8549019608, + 0.6039215686, + 0.0, + 0.8549019607843137, + 0.862745098, + 0.6117647059, + 0.0, + 0.8588235294117647, + 0.8784313725, + 0.6196078431, + 0.0, + 0.8627450980392157, + 0.8862745098, + 0.6352941176, + 0.0, + 0.8666666666666667, + 0.8941176471, + 0.6431372549, + 0.0, + 0.8705882352941177, + 0.9098039216, + 0.6509803922, + 0.0, + 0.8745098039215686, + 0.9176470588, + 0.6666666667, + 0.0, + 0.8784313725490196, + 0.9254901961, + 0.6745098039, + 0.0, + 0.8823529411764706, + 0.9411764706, + 0.6823529412, + 0.0, + 0.8862745098039215, + 0.9490196078, + 0.6980392157, + 0.0, + 0.8901960784313725, + 0.9568627451, + 0.7058823529, + 0.0, + 0.8941176470588236, + 0.9647058824, + 0.7137254902, + 0.0, + 0.8980392156862745, + 0.9803921569, + 0.7294117647, + 0.0, + 0.9019607843137255, + 0.9882352941, + 0.737254902, + 0.0, + 0.9058823529411765, + 0.9960784314, + 0.7450980392, + 0.0, + 0.9098039215686274, + 0.9960784314, + 0.7607843137, + 0.0, + 0.9137254901960784, + 0.9960784314, + 0.768627451, + 0.0, + 0.9176470588235294, + 0.9960784314, + 0.7764705882, + 0.0, + 0.9215686274509803, + 0.9960784314, + 0.7921568627, + 0.0, + 0.9254901960784314, + 0.9960784314, + 0.8, + 0.0, + 0.9294117647058824, + 0.9960784314, + 0.8078431373, + 0.0, + 0.9333333333333333, + 0.9960784314, + 0.8235294118, + 0.0, + 0.9372549019607843, + 0.9960784314, + 0.831372549, + 0.0, + 0.9411764705882354, + 0.9960784314, + 0.8392156863, + 0.0, + 0.9450980392156864, + 0.9960784314, + 0.8549019608, + 0.0, + 0.9490196078431372, + 0.9960784314, + 0.862745098, + 0.0549019608, + 0.9529411764705882, + 0.9960784314, + 0.8705882353, + 0.1098039216, + 0.9568627450980394, + 0.9960784314, + 0.8862745098, + 0.1647058824, + 0.9607843137254903, + 0.9960784314, + 0.8941176471, + 0.2196078431, + 0.9647058823529413, + 0.9960784314, + 0.9019607843, + 0.2666666667, + 0.9686274509803922, + 0.9960784314, + 0.9176470588, + 0.3215686275, + 0.9725490196078431, + 0.9960784314, + 0.9254901961, + 0.3764705882, + 0.9764705882352941, + 0.9960784314, + 0.9333333333, + 0.431372549, + 0.9803921568627451, + 0.9960784314, + 0.9490196078, + 0.4862745098, + 0.984313725490196, + 0.9960784314, + 0.9568627451, + 0.5333333333, + 0.9882352941176471, + 0.9960784314, + 0.9647058824, + 0.5882352941, + 0.9921568627450981, + 0.9960784314, + 0.9803921569, + 0.6431372549, + 0.996078431372549, + 0.9960784314, + 0.9882352941, + 0.6980392157, + 1.0, + 0.9960784314, + 0.9960784314, + 0.7450980392, + ], + }, + { + ColorSpace: 'RGB', + Name: 'rainbow_2', + RGBPoints: [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.00392156862745098, + 0.0156862745, + 0.0, + 0.0117647059, + 0.00784313725490196, + 0.0352941176, + 0.0, + 0.0274509804, + 0.011764705882352941, + 0.0509803922, + 0.0, + 0.0392156863, + 0.01568627450980392, + 0.0705882353, + 0.0, + 0.0549019608, + 0.0196078431372549, + 0.0862745098, + 0.0, + 0.0745098039, + 0.023529411764705882, + 0.1058823529, + 0.0, + 0.0901960784, + 0.027450980392156862, + 0.1215686275, + 0.0, + 0.1098039216, + 0.03137254901960784, + 0.1411764706, + 0.0, + 0.1254901961, + 0.03529411764705882, + 0.1568627451, + 0.0, + 0.1490196078, + 0.0392156862745098, + 0.1764705882, + 0.0, + 0.168627451, + 0.043137254901960784, + 0.1960784314, + 0.0, + 0.1882352941, + 0.047058823529411764, + 0.2117647059, + 0.0, + 0.2078431373, + 0.050980392156862744, + 0.2274509804, + 0.0, + 0.231372549, + 0.054901960784313725, + 0.2392156863, + 0.0, + 0.2470588235, + 0.05882352941176471, + 0.2509803922, + 0.0, + 0.2666666667, + 0.06274509803921569, + 0.2666666667, + 0.0, + 0.2823529412, + 0.06666666666666667, + 0.2705882353, + 0.0, + 0.3019607843, + 0.07058823529411765, + 0.2823529412, + 0.0, + 0.3176470588, + 0.07450980392156863, + 0.2901960784, + 0.0, + 0.337254902, + 0.0784313725490196, + 0.3019607843, + 0.0, + 0.3568627451, + 0.08235294117647059, + 0.3098039216, + 0.0, + 0.3725490196, + 0.08627450980392157, + 0.3137254902, + 0.0, + 0.3921568627, + 0.09019607843137255, + 0.3215686275, + 0.0, + 0.4078431373, + 0.09411764705882353, + 0.3254901961, + 0.0, + 0.4274509804, + 0.09803921568627451, + 0.3333333333, + 0.0, + 0.4431372549, + 0.10196078431372549, + 0.3294117647, + 0.0, + 0.462745098, + 0.10588235294117647, + 0.337254902, + 0.0, + 0.4784313725, + 0.10980392156862745, + 0.3411764706, + 0.0, + 0.4980392157, + 0.11372549019607843, + 0.3450980392, + 0.0, + 0.5176470588, + 0.11764705882352942, + 0.337254902, + 0.0, + 0.5333333333, + 0.12156862745098039, + 0.3411764706, + 0.0, + 0.5529411765, + 0.12549019607843137, + 0.3411764706, + 0.0, + 0.568627451, + 0.12941176470588237, + 0.3411764706, + 0.0, + 0.5882352941, + 0.13333333333333333, + 0.3333333333, + 0.0, + 0.6039215686, + 0.13725490196078433, + 0.3294117647, + 0.0, + 0.6235294118, + 0.1411764705882353, + 0.3294117647, + 0.0, + 0.6392156863, + 0.1450980392156863, + 0.3294117647, + 0.0, + 0.6588235294, + 0.14901960784313725, + 0.3254901961, + 0.0, + 0.6784313725, + 0.15294117647058825, + 0.3098039216, + 0.0, + 0.6941176471, + 0.1568627450980392, + 0.3058823529, + 0.0, + 0.7137254902, + 0.1607843137254902, + 0.3019607843, + 0.0, + 0.7294117647, + 0.16470588235294117, + 0.2980392157, + 0.0, + 0.7490196078, + 0.16862745098039217, + 0.2784313725, + 0.0, + 0.7647058824, + 0.17254901960784313, + 0.2745098039, + 0.0, + 0.7843137255, + 0.17647058823529413, + 0.2666666667, + 0.0, + 0.8, + 0.1803921568627451, + 0.2588235294, + 0.0, + 0.8196078431, + 0.1843137254901961, + 0.2352941176, + 0.0, + 0.8392156863, + 0.18823529411764706, + 0.2274509804, + 0.0, + 0.8549019608, + 0.19215686274509805, + 0.2156862745, + 0.0, + 0.8745098039, + 0.19607843137254902, + 0.2078431373, + 0.0, + 0.8901960784, + 0.2, + 0.1803921569, + 0.0, + 0.9098039216, + 0.20392156862745098, + 0.168627451, + 0.0, + 0.9254901961, + 0.20784313725490197, + 0.1568627451, + 0.0, + 0.9450980392, + 0.21176470588235294, + 0.1411764706, + 0.0, + 0.9607843137, + 0.21568627450980393, + 0.1294117647, + 0.0, + 0.9803921569, + 0.2196078431372549, + 0.0980392157, + 0.0, + 1.0, + 0.2235294117647059, + 0.0823529412, + 0.0, + 1.0, + 0.22745098039215686, + 0.062745098, + 0.0, + 1.0, + 0.23137254901960785, + 0.0470588235, + 0.0, + 1.0, + 0.23529411764705885, + 0.0156862745, + 0.0, + 1.0, + 0.23921568627450984, + 0.0, + 0.0, + 1.0, + 0.24313725490196078, + 0.0, + 0.0156862745, + 1.0, + 0.24705882352941178, + 0.0, + 0.031372549, + 1.0, + 0.25098039215686274, + 0.0, + 0.062745098, + 1.0, + 0.2549019607843137, + 0.0, + 0.0823529412, + 1.0, + 0.25882352941176473, + 0.0, + 0.0980392157, + 1.0, + 0.2627450980392157, + 0.0, + 0.1137254902, + 1.0, + 0.26666666666666666, + 0.0, + 0.1490196078, + 1.0, + 0.27058823529411763, + 0.0, + 0.1647058824, + 1.0, + 0.27450980392156865, + 0.0, + 0.1803921569, + 1.0, + 0.2784313725490196, + 0.0, + 0.2, + 1.0, + 0.2823529411764706, + 0.0, + 0.2156862745, + 1.0, + 0.28627450980392155, + 0.0, + 0.2470588235, + 1.0, + 0.2901960784313726, + 0.0, + 0.262745098, + 1.0, + 0.29411764705882354, + 0.0, + 0.2823529412, + 1.0, + 0.2980392156862745, + 0.0, + 0.2980392157, + 1.0, + 0.30196078431372547, + 0.0, + 0.3294117647, + 1.0, + 0.3058823529411765, + 0.0, + 0.3490196078, + 1.0, + 0.30980392156862746, + 0.0, + 0.3647058824, + 1.0, + 0.3137254901960784, + 0.0, + 0.3803921569, + 1.0, + 0.3176470588235294, + 0.0, + 0.4156862745, + 1.0, + 0.3215686274509804, + 0.0, + 0.431372549, + 1.0, + 0.3254901960784314, + 0.0, + 0.4470588235, + 1.0, + 0.32941176470588235, + 0.0, + 0.4666666667, + 1.0, + 0.3333333333333333, + 0.0, + 0.4980392157, + 1.0, + 0.33725490196078434, + 0.0, + 0.5137254902, + 1.0, + 0.3411764705882353, + 0.0, + 0.5294117647, + 1.0, + 0.34509803921568627, + 0.0, + 0.5490196078, + 1.0, + 0.34901960784313724, + 0.0, + 0.5647058824, + 1.0, + 0.35294117647058826, + 0.0, + 0.5960784314, + 1.0, + 0.3568627450980392, + 0.0, + 0.6156862745, + 1.0, + 0.3607843137254902, + 0.0, + 0.631372549, + 1.0, + 0.36470588235294116, + 0.0, + 0.6470588235, + 1.0, + 0.3686274509803922, + 0.0, + 0.6823529412, + 1.0, + 0.37254901960784315, + 0.0, + 0.6980392157, + 1.0, + 0.3764705882352941, + 0.0, + 0.7137254902, + 1.0, + 0.3803921568627451, + 0.0, + 0.7333333333, + 1.0, + 0.3843137254901961, + 0.0, + 0.7647058824, + 1.0, + 0.38823529411764707, + 0.0, + 0.7803921569, + 1.0, + 0.39215686274509803, + 0.0, + 0.7960784314, + 1.0, + 0.396078431372549, + 0.0, + 0.8156862745, + 1.0, + 0.4, + 0.0, + 0.8470588235, + 1.0, + 0.403921568627451, + 0.0, + 0.862745098, + 1.0, + 0.40784313725490196, + 0.0, + 0.8823529412, + 1.0, + 0.4117647058823529, + 0.0, + 0.8980392157, + 1.0, + 0.41568627450980394, + 0.0, + 0.9137254902, + 1.0, + 0.4196078431372549, + 0.0, + 0.9490196078, + 1.0, + 0.4235294117647059, + 0.0, + 0.9647058824, + 1.0, + 0.42745098039215684, + 0.0, + 0.9803921569, + 1.0, + 0.43137254901960786, + 0.0, + 1.0, + 1.0, + 0.43529411764705883, + 0.0, + 1.0, + 0.9647058824, + 0.4392156862745098, + 0.0, + 1.0, + 0.9490196078, + 0.44313725490196076, + 0.0, + 1.0, + 0.9333333333, + 0.4470588235294118, + 0.0, + 1.0, + 0.9137254902, + 0.45098039215686275, + 0.0, + 1.0, + 0.8823529412, + 0.4549019607843137, + 0.0, + 1.0, + 0.862745098, + 0.4588235294117647, + 0.0, + 1.0, + 0.8470588235, + 0.4627450980392157, + 0.0, + 1.0, + 0.831372549, + 0.4666666666666667, + 0.0, + 1.0, + 0.7960784314, + 0.4705882352941177, + 0.0, + 1.0, + 0.7803921569, + 0.4745098039215686, + 0.0, + 1.0, + 0.7647058824, + 0.4784313725490197, + 0.0, + 1.0, + 0.7490196078, + 0.48235294117647065, + 0.0, + 1.0, + 0.7333333333, + 0.48627450980392156, + 0.0, + 1.0, + 0.6980392157, + 0.49019607843137253, + 0.0, + 1.0, + 0.6823529412, + 0.49411764705882355, + 0.0, + 1.0, + 0.6666666667, + 0.4980392156862745, + 0.0, + 1.0, + 0.6470588235, + 0.5019607843137255, + 0.0, + 1.0, + 0.6156862745, + 0.5058823529411764, + 0.0, + 1.0, + 0.5960784314, + 0.5098039215686274, + 0.0, + 1.0, + 0.5803921569, + 0.5137254901960784, + 0.0, + 1.0, + 0.5647058824, + 0.5176470588235295, + 0.0, + 1.0, + 0.5294117647, + 0.5215686274509804, + 0.0, + 1.0, + 0.5137254902, + 0.5254901960784314, + 0.0, + 1.0, + 0.4980392157, + 0.5294117647058824, + 0.0, + 1.0, + 0.4823529412, + 0.5333333333333333, + 0.0, + 1.0, + 0.4470588235, + 0.5372549019607843, + 0.0, + 1.0, + 0.431372549, + 0.5411764705882353, + 0.0, + 1.0, + 0.4156862745, + 0.5450980392156862, + 0.0, + 1.0, + 0.4, + 0.5490196078431373, + 0.0, + 1.0, + 0.3803921569, + 0.5529411764705883, + 0.0, + 1.0, + 0.3490196078, + 0.5568627450980392, + 0.0, + 1.0, + 0.3294117647, + 0.5607843137254902, + 0.0, + 1.0, + 0.3137254902, + 0.5647058823529412, + 0.0, + 1.0, + 0.2980392157, + 0.5686274509803921, + 0.0, + 1.0, + 0.262745098, + 0.5725490196078431, + 0.0, + 1.0, + 0.2470588235, + 0.5764705882352941, + 0.0, + 1.0, + 0.231372549, + 0.5803921568627451, + 0.0, + 1.0, + 0.2156862745, + 0.5843137254901961, + 0.0, + 1.0, + 0.1803921569, + 0.5882352941176471, + 0.0, + 1.0, + 0.1647058824, + 0.592156862745098, + 0.0, + 1.0, + 0.1490196078, + 0.596078431372549, + 0.0, + 1.0, + 0.1333333333, + 0.6, + 0.0, + 1.0, + 0.0980392157, + 0.6039215686274509, + 0.0, + 1.0, + 0.0823529412, + 0.6078431372549019, + 0.0, + 1.0, + 0.062745098, + 0.611764705882353, + 0.0, + 1.0, + 0.0470588235, + 0.615686274509804, + 0.0, + 1.0, + 0.031372549, + 0.6196078431372549, + 0.0, + 1.0, + 0.0, + 0.6235294117647059, + 0.0156862745, + 1.0, + 0.0, + 0.6274509803921569, + 0.031372549, + 1.0, + 0.0, + 0.6313725490196078, + 0.0470588235, + 1.0, + 0.0, + 0.6352941176470588, + 0.0823529412, + 1.0, + 0.0, + 0.6392156862745098, + 0.0980392157, + 1.0, + 0.0, + 0.6431372549019608, + 0.1137254902, + 1.0, + 0.0, + 0.6470588235294118, + 0.1294117647, + 1.0, + 0.0, + 0.6509803921568628, + 0.1647058824, + 1.0, + 0.0, + 0.6549019607843137, + 0.1803921569, + 1.0, + 0.0, + 0.6588235294117647, + 0.2, + 1.0, + 0.0, + 0.6627450980392157, + 0.2156862745, + 1.0, + 0.0, + 0.6666666666666666, + 0.2470588235, + 1.0, + 0.0, + 0.6705882352941176, + 0.262745098, + 1.0, + 0.0, + 0.6745098039215687, + 0.2823529412, + 1.0, + 0.0, + 0.6784313725490196, + 0.2980392157, + 1.0, + 0.0, + 0.6823529411764706, + 0.3137254902, + 1.0, + 0.0, + 0.6862745098039216, + 0.3490196078, + 1.0, + 0.0, + 0.6901960784313725, + 0.3647058824, + 1.0, + 0.0, + 0.6941176470588235, + 0.3803921569, + 1.0, + 0.0, + 0.6980392156862745, + 0.3960784314, + 1.0, + 0.0, + 0.7019607843137254, + 0.431372549, + 1.0, + 0.0, + 0.7058823529411765, + 0.4470588235, + 1.0, + 0.0, + 0.7098039215686275, + 0.4666666667, + 1.0, + 0.0, + 0.7137254901960784, + 0.4823529412, + 1.0, + 0.0, + 0.7176470588235294, + 0.5137254902, + 1.0, + 0.0, + 0.7215686274509804, + 0.5294117647, + 1.0, + 0.0, + 0.7254901960784313, + 0.5490196078, + 1.0, + 0.0, + 0.7294117647058823, + 0.5647058824, + 1.0, + 0.0, + 0.7333333333333333, + 0.6, + 1.0, + 0.0, + 0.7372549019607844, + 0.6156862745, + 1.0, + 0.0, + 0.7411764705882353, + 0.631372549, + 1.0, + 0.0, + 0.7450980392156863, + 0.6470588235, + 1.0, + 0.0, + 0.7490196078431373, + 0.662745098, + 1.0, + 0.0, + 0.7529411764705882, + 0.6980392157, + 1.0, + 0.0, + 0.7568627450980392, + 0.7137254902, + 1.0, + 0.0, + 0.7607843137254902, + 0.7333333333, + 1.0, + 0.0, + 0.7647058823529411, + 0.7490196078, + 1.0, + 0.0, + 0.7686274509803922, + 0.7803921569, + 1.0, + 0.0, + 0.7725490196078432, + 0.7960784314, + 1.0, + 0.0, + 0.7764705882352941, + 0.8156862745, + 1.0, + 0.0, + 0.7803921568627451, + 0.831372549, + 1.0, + 0.0, + 0.7843137254901961, + 0.8666666667, + 1.0, + 0.0, + 0.788235294117647, + 0.8823529412, + 1.0, + 0.0, + 0.792156862745098, + 0.8980392157, + 1.0, + 0.0, + 0.796078431372549, + 0.9137254902, + 1.0, + 0.0, + 0.8, + 0.9490196078, + 1.0, + 0.0, + 0.803921568627451, + 0.9647058824, + 1.0, + 0.0, + 0.807843137254902, + 0.9803921569, + 1.0, + 0.0, + 0.8117647058823529, + 1.0, + 1.0, + 0.0, + 0.8156862745098039, + 1.0, + 0.9803921569, + 0.0, + 0.8196078431372549, + 1.0, + 0.9490196078, + 0.0, + 0.8235294117647058, + 1.0, + 0.9333333333, + 0.0, + 0.8274509803921568, + 1.0, + 0.9137254902, + 0.0, + 0.8313725490196079, + 1.0, + 0.8980392157, + 0.0, + 0.8352941176470589, + 1.0, + 0.8666666667, + 0.0, + 0.8392156862745098, + 1.0, + 0.8470588235, + 0.0, + 0.8431372549019608, + 1.0, + 0.831372549, + 0.0, + 0.8470588235294118, + 1.0, + 0.8156862745, + 0.0, + 0.8509803921568627, + 1.0, + 0.7803921569, + 0.0, + 0.8549019607843137, + 1.0, + 0.7647058824, + 0.0, + 0.8588235294117647, + 1.0, + 0.7490196078, + 0.0, + 0.8627450980392157, + 1.0, + 0.7333333333, + 0.0, + 0.8666666666666667, + 1.0, + 0.6980392157, + 0.0, + 0.8705882352941177, + 1.0, + 0.6823529412, + 0.0, + 0.8745098039215686, + 1.0, + 0.6666666667, + 0.0, + 0.8784313725490196, + 1.0, + 0.6470588235, + 0.0, + 0.8823529411764706, + 1.0, + 0.631372549, + 0.0, + 0.8862745098039215, + 1.0, + 0.6, + 0.0, + 0.8901960784313725, + 1.0, + 0.5803921569, + 0.0, + 0.8941176470588236, + 1.0, + 0.5647058824, + 0.0, + 0.8980392156862745, + 1.0, + 0.5490196078, + 0.0, + 0.9019607843137255, + 1.0, + 0.5137254902, + 0.0, + 0.9058823529411765, + 1.0, + 0.4980392157, + 0.0, + 0.9098039215686274, + 1.0, + 0.4823529412, + 0.0, + 0.9137254901960784, + 1.0, + 0.4666666667, + 0.0, + 0.9176470588235294, + 1.0, + 0.431372549, + 0.0, + 0.9215686274509803, + 1.0, + 0.4156862745, + 0.0, + 0.9254901960784314, + 1.0, + 0.4, + 0.0, + 0.9294117647058824, + 1.0, + 0.3803921569, + 0.0, + 0.9333333333333333, + 1.0, + 0.3490196078, + 0.0, + 0.9372549019607843, + 1.0, + 0.3333333333, + 0.0, + 0.9411764705882354, + 1.0, + 0.3137254902, + 0.0, + 0.9450980392156864, + 1.0, + 0.2980392157, + 0.0, + 0.9490196078431372, + 1.0, + 0.2823529412, + 0.0, + 0.9529411764705882, + 1.0, + 0.2470588235, + 0.0, + 0.9568627450980394, + 1.0, + 0.231372549, + 0.0, + 0.9607843137254903, + 1.0, + 0.2156862745, + 0.0, + 0.9647058823529413, + 1.0, + 0.2, + 0.0, + 0.9686274509803922, + 1.0, + 0.1647058824, + 0.0, + 0.9725490196078431, + 1.0, + 0.1490196078, + 0.0, + 0.9764705882352941, + 1.0, + 0.1333333333, + 0.0, + 0.9803921568627451, + 1.0, + 0.1137254902, + 0.0, + 0.984313725490196, + 1.0, + 0.0823529412, + 0.0, + 0.9882352941176471, + 1.0, + 0.0666666667, + 0.0, + 0.9921568627450981, + 1.0, + 0.0470588235, + 0.0, + 0.996078431372549, + 1.0, + 0.031372549, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + ], + }, + { + ColorSpace: 'RGB', + Name: 'suv', + RGBPoints: [ + 0.0, + 1.0, + 1.0, + 1.0, + 0.00392156862745098, + 1.0, + 1.0, + 1.0, + 0.00784313725490196, + 1.0, + 1.0, + 1.0, + 0.011764705882352941, + 1.0, + 1.0, + 1.0, + 0.01568627450980392, + 1.0, + 1.0, + 1.0, + 0.0196078431372549, + 1.0, + 1.0, + 1.0, + 0.023529411764705882, + 1.0, + 1.0, + 1.0, + 0.027450980392156862, + 1.0, + 1.0, + 1.0, + 0.03137254901960784, + 1.0, + 1.0, + 1.0, + 0.03529411764705882, + 1.0, + 1.0, + 1.0, + 0.0392156862745098, + 1.0, + 1.0, + 1.0, + 0.043137254901960784, + 1.0, + 1.0, + 1.0, + 0.047058823529411764, + 1.0, + 1.0, + 1.0, + 0.050980392156862744, + 1.0, + 1.0, + 1.0, + 0.054901960784313725, + 1.0, + 1.0, + 1.0, + 0.05882352941176471, + 1.0, + 1.0, + 1.0, + 0.06274509803921569, + 1.0, + 1.0, + 1.0, + 0.06666666666666667, + 1.0, + 1.0, + 1.0, + 0.07058823529411765, + 1.0, + 1.0, + 1.0, + 0.07450980392156863, + 1.0, + 1.0, + 1.0, + 0.0784313725490196, + 1.0, + 1.0, + 1.0, + 0.08235294117647059, + 1.0, + 1.0, + 1.0, + 0.08627450980392157, + 1.0, + 1.0, + 1.0, + 0.09019607843137255, + 1.0, + 1.0, + 1.0, + 0.09411764705882353, + 1.0, + 1.0, + 1.0, + 0.09803921568627451, + 1.0, + 1.0, + 1.0, + 0.10196078431372549, + 0.737254902, + 0.737254902, + 0.737254902, + 0.10588235294117647, + 0.737254902, + 0.737254902, + 0.737254902, + 0.10980392156862745, + 0.737254902, + 0.737254902, + 0.737254902, + 0.11372549019607843, + 0.737254902, + 0.737254902, + 0.737254902, + 0.11764705882352942, + 0.737254902, + 0.737254902, + 0.737254902, + 0.12156862745098039, + 0.737254902, + 0.737254902, + 0.737254902, + 0.12549019607843137, + 0.737254902, + 0.737254902, + 0.737254902, + 0.12941176470588237, + 0.737254902, + 0.737254902, + 0.737254902, + 0.13333333333333333, + 0.737254902, + 0.737254902, + 0.737254902, + 0.13725490196078433, + 0.737254902, + 0.737254902, + 0.737254902, + 0.1411764705882353, + 0.737254902, + 0.737254902, + 0.737254902, + 0.1450980392156863, + 0.737254902, + 0.737254902, + 0.737254902, + 0.14901960784313725, + 0.737254902, + 0.737254902, + 0.737254902, + 0.15294117647058825, + 0.737254902, + 0.737254902, + 0.737254902, + 0.1568627450980392, + 0.737254902, + 0.737254902, + 0.737254902, + 0.1607843137254902, + 0.737254902, + 0.737254902, + 0.737254902, + 0.16470588235294117, + 0.737254902, + 0.737254902, + 0.737254902, + 0.16862745098039217, + 0.737254902, + 0.737254902, + 0.737254902, + 0.17254901960784313, + 0.737254902, + 0.737254902, + 0.737254902, + 0.17647058823529413, + 0.737254902, + 0.737254902, + 0.737254902, + 0.1803921568627451, + 0.737254902, + 0.737254902, + 0.737254902, + 0.1843137254901961, + 0.737254902, + 0.737254902, + 0.737254902, + 0.18823529411764706, + 0.737254902, + 0.737254902, + 0.737254902, + 0.19215686274509805, + 0.737254902, + 0.737254902, + 0.737254902, + 0.19607843137254902, + 0.737254902, + 0.737254902, + 0.737254902, + 0.2, + 0.737254902, + 0.737254902, + 0.737254902, + 0.20392156862745098, + 0.431372549, + 0.0, + 0.568627451, + 0.20784313725490197, + 0.431372549, + 0.0, + 0.568627451, + 0.21176470588235294, + 0.431372549, + 0.0, + 0.568627451, + 0.21568627450980393, + 0.431372549, + 0.0, + 0.568627451, + 0.2196078431372549, + 0.431372549, + 0.0, + 0.568627451, + 0.2235294117647059, + 0.431372549, + 0.0, + 0.568627451, + 0.22745098039215686, + 0.431372549, + 0.0, + 0.568627451, + 0.23137254901960785, + 0.431372549, + 0.0, + 0.568627451, + 0.23529411764705885, + 0.431372549, + 0.0, + 0.568627451, + 0.23921568627450984, + 0.431372549, + 0.0, + 0.568627451, + 0.24313725490196078, + 0.431372549, + 0.0, + 0.568627451, + 0.24705882352941178, + 0.431372549, + 0.0, + 0.568627451, + 0.25098039215686274, + 0.431372549, + 0.0, + 0.568627451, + 0.2549019607843137, + 0.431372549, + 0.0, + 0.568627451, + 0.25882352941176473, + 0.431372549, + 0.0, + 0.568627451, + 0.2627450980392157, + 0.431372549, + 0.0, + 0.568627451, + 0.26666666666666666, + 0.431372549, + 0.0, + 0.568627451, + 0.27058823529411763, + 0.431372549, + 0.0, + 0.568627451, + 0.27450980392156865, + 0.431372549, + 0.0, + 0.568627451, + 0.2784313725490196, + 0.431372549, + 0.0, + 0.568627451, + 0.2823529411764706, + 0.431372549, + 0.0, + 0.568627451, + 0.28627450980392155, + 0.431372549, + 0.0, + 0.568627451, + 0.2901960784313726, + 0.431372549, + 0.0, + 0.568627451, + 0.29411764705882354, + 0.431372549, + 0.0, + 0.568627451, + 0.2980392156862745, + 0.431372549, + 0.0, + 0.568627451, + 0.30196078431372547, + 0.431372549, + 0.0, + 0.568627451, + 0.3058823529411765, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.30980392156862746, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3137254901960784, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3176470588235294, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3215686274509804, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3254901960784314, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.32941176470588235, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3333333333333333, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.33725490196078434, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3411764705882353, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.34509803921568627, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.34901960784313724, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.35294117647058826, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3568627450980392, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3607843137254902, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.36470588235294116, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3686274509803922, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.37254901960784315, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3764705882352941, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3803921568627451, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.3843137254901961, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.38823529411764707, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.39215686274509803, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.396078431372549, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.4, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.403921568627451, + 0.2509803922, + 0.3333333333, + 0.6509803922, + 0.40784313725490196, + 0.0, + 0.8, + 1.0, + 0.4117647058823529, + 0.0, + 0.8, + 1.0, + 0.41568627450980394, + 0.0, + 0.8, + 1.0, + 0.4196078431372549, + 0.0, + 0.8, + 1.0, + 0.4235294117647059, + 0.0, + 0.8, + 1.0, + 0.42745098039215684, + 0.0, + 0.8, + 1.0, + 0.43137254901960786, + 0.0, + 0.8, + 1.0, + 0.43529411764705883, + 0.0, + 0.8, + 1.0, + 0.4392156862745098, + 0.0, + 0.8, + 1.0, + 0.44313725490196076, + 0.0, + 0.8, + 1.0, + 0.4470588235294118, + 0.0, + 0.8, + 1.0, + 0.45098039215686275, + 0.0, + 0.8, + 1.0, + 0.4549019607843137, + 0.0, + 0.8, + 1.0, + 0.4588235294117647, + 0.0, + 0.8, + 1.0, + 0.4627450980392157, + 0.0, + 0.8, + 1.0, + 0.4666666666666667, + 0.0, + 0.8, + 1.0, + 0.4705882352941177, + 0.0, + 0.8, + 1.0, + 0.4745098039215686, + 0.0, + 0.8, + 1.0, + 0.4784313725490197, + 0.0, + 0.8, + 1.0, + 0.48235294117647065, + 0.0, + 0.8, + 1.0, + 0.48627450980392156, + 0.0, + 0.8, + 1.0, + 0.49019607843137253, + 0.0, + 0.8, + 1.0, + 0.49411764705882355, + 0.0, + 0.8, + 1.0, + 0.4980392156862745, + 0.0, + 0.8, + 1.0, + 0.5019607843137255, + 0.0, + 0.8, + 1.0, + 0.5058823529411764, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5098039215686274, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5137254901960784, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5176470588235295, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5215686274509804, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5254901960784314, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5294117647058824, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5333333333333333, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5372549019607843, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5411764705882353, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5450980392156862, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5490196078431373, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5529411764705883, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5568627450980392, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5607843137254902, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5647058823529412, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5686274509803921, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5725490196078431, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5764705882352941, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5803921568627451, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5843137254901961, + 0.0, + 0.6666666667, + 0.5333333333, + 0.5882352941176471, + 0.0, + 0.6666666667, + 0.5333333333, + 0.592156862745098, + 0.0, + 0.6666666667, + 0.5333333333, + 0.596078431372549, + 0.0, + 0.6666666667, + 0.5333333333, + 0.6, + 0.0, + 0.6666666667, + 0.5333333333, + 0.6039215686274509, + 0.0, + 0.6666666667, + 0.5333333333, + 0.6078431372549019, + 0.4, + 1.0, + 0.4, + 0.611764705882353, + 0.4, + 1.0, + 0.4, + 0.615686274509804, + 0.4, + 1.0, + 0.4, + 0.6196078431372549, + 0.4, + 1.0, + 0.4, + 0.6235294117647059, + 0.4, + 1.0, + 0.4, + 0.6274509803921569, + 0.4, + 1.0, + 0.4, + 0.6313725490196078, + 0.4, + 1.0, + 0.4, + 0.6352941176470588, + 0.4, + 1.0, + 0.4, + 0.6392156862745098, + 0.4, + 1.0, + 0.4, + 0.6431372549019608, + 0.4, + 1.0, + 0.4, + 0.6470588235294118, + 0.4, + 1.0, + 0.4, + 0.6509803921568628, + 0.4, + 1.0, + 0.4, + 0.6549019607843137, + 0.4, + 1.0, + 0.4, + 0.6588235294117647, + 0.4, + 1.0, + 0.4, + 0.6627450980392157, + 0.4, + 1.0, + 0.4, + 0.6666666666666666, + 0.4, + 1.0, + 0.4, + 0.6705882352941176, + 0.4, + 1.0, + 0.4, + 0.6745098039215687, + 0.4, + 1.0, + 0.4, + 0.6784313725490196, + 0.4, + 1.0, + 0.4, + 0.6823529411764706, + 0.4, + 1.0, + 0.4, + 0.6862745098039216, + 0.4, + 1.0, + 0.4, + 0.6901960784313725, + 0.4, + 1.0, + 0.4, + 0.6941176470588235, + 0.4, + 1.0, + 0.4, + 0.6980392156862745, + 0.4, + 1.0, + 0.4, + 0.7019607843137254, + 0.4, + 1.0, + 0.4, + 0.7058823529411765, + 1.0, + 0.9490196078, + 0.0, + 0.7098039215686275, + 1.0, + 0.9490196078, + 0.0, + 0.7137254901960784, + 1.0, + 0.9490196078, + 0.0, + 0.7176470588235294, + 1.0, + 0.9490196078, + 0.0, + 0.7215686274509804, + 1.0, + 0.9490196078, + 0.0, + 0.7254901960784313, + 1.0, + 0.9490196078, + 0.0, + 0.7294117647058823, + 1.0, + 0.9490196078, + 0.0, + 0.7333333333333333, + 1.0, + 0.9490196078, + 0.0, + 0.7372549019607844, + 1.0, + 0.9490196078, + 0.0, + 0.7411764705882353, + 1.0, + 0.9490196078, + 0.0, + 0.7450980392156863, + 1.0, + 0.9490196078, + 0.0, + 0.7490196078431373, + 1.0, + 0.9490196078, + 0.0, + 0.7529411764705882, + 1.0, + 0.9490196078, + 0.0, + 0.7568627450980392, + 1.0, + 0.9490196078, + 0.0, + 0.7607843137254902, + 1.0, + 0.9490196078, + 0.0, + 0.7647058823529411, + 1.0, + 0.9490196078, + 0.0, + 0.7686274509803922, + 1.0, + 0.9490196078, + 0.0, + 0.7725490196078432, + 1.0, + 0.9490196078, + 0.0, + 0.7764705882352941, + 1.0, + 0.9490196078, + 0.0, + 0.7803921568627451, + 1.0, + 0.9490196078, + 0.0, + 0.7843137254901961, + 1.0, + 0.9490196078, + 0.0, + 0.788235294117647, + 1.0, + 0.9490196078, + 0.0, + 0.792156862745098, + 1.0, + 0.9490196078, + 0.0, + 0.796078431372549, + 1.0, + 0.9490196078, + 0.0, + 0.8, + 1.0, + 0.9490196078, + 0.0, + 0.803921568627451, + 1.0, + 0.9490196078, + 0.0, + 0.807843137254902, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8117647058823529, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8156862745098039, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8196078431372549, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8235294117647058, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8274509803921568, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8313725490196079, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8352941176470589, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8392156862745098, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8431372549019608, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8470588235294118, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8509803921568627, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8549019607843137, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8588235294117647, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8627450980392157, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8666666666666667, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8705882352941177, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8745098039215686, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8784313725490196, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8823529411764706, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8862745098039215, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8901960784313725, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8941176470588236, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.8980392156862745, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.9019607843137255, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.9058823529411765, + 0.9490196078, + 0.6509803922, + 0.2509803922, + 0.9098039215686274, + 1.0, + 0.0, + 0.0, + 0.9137254901960784, + 1.0, + 0.0, + 0.0, + 0.9176470588235294, + 1.0, + 0.0, + 0.0, + 0.9215686274509803, + 1.0, + 0.0, + 0.0, + 0.9254901960784314, + 1.0, + 0.0, + 0.0, + 0.9294117647058824, + 1.0, + 0.0, + 0.0, + 0.9333333333333333, + 1.0, + 0.0, + 0.0, + 0.9372549019607843, + 1.0, + 0.0, + 0.0, + 0.9411764705882354, + 1.0, + 0.0, + 0.0, + 0.9450980392156864, + 1.0, + 0.0, + 0.0, + 0.9490196078431372, + 1.0, + 0.0, + 0.0, + 0.9529411764705882, + 1.0, + 0.0, + 0.0, + 0.9568627450980394, + 1.0, + 0.0, + 0.0, + 0.9607843137254903, + 1.0, + 0.0, + 0.0, + 0.9647058823529413, + 1.0, + 0.0, + 0.0, + 0.9686274509803922, + 1.0, + 0.0, + 0.0, + 0.9725490196078431, + 1.0, + 0.0, + 0.0, + 0.9764705882352941, + 1.0, + 0.0, + 0.0, + 0.9803921568627451, + 1.0, + 0.0, + 0.0, + 0.984313725490196, + 1.0, + 0.0, + 0.0, + 0.9882352941176471, + 1.0, + 0.0, + 0.0, + 0.9921568627450981, + 1.0, + 0.0, + 0.0, + 0.996078431372549, + 1.0, + 0.0, + 0.0, + 1.0, + 1.0, + 0.0, + 0.0, + ], + }, + { + ColorSpace: 'RGB', + Name: 'ge_256', + RGBPoints: [ + 0.0, + 0.0039215686, + 0.0078431373, + 0.0078431373, + 0.00392156862745098, + 0.0039215686, + 0.0078431373, + 0.0078431373, + 0.00784313725490196, + 0.0039215686, + 0.0078431373, + 0.0117647059, + 0.011764705882352941, + 0.0039215686, + 0.0117647059, + 0.0156862745, + 0.01568627450980392, + 0.0039215686, + 0.0117647059, + 0.0196078431, + 0.0196078431372549, + 0.0039215686, + 0.0156862745, + 0.0235294118, + 0.023529411764705882, + 0.0039215686, + 0.0156862745, + 0.0274509804, + 0.027450980392156862, + 0.0039215686, + 0.0196078431, + 0.031372549, + 0.03137254901960784, + 0.0039215686, + 0.0196078431, + 0.0352941176, + 0.03529411764705882, + 0.0039215686, + 0.0235294118, + 0.0392156863, + 0.0392156862745098, + 0.0039215686, + 0.0235294118, + 0.0431372549, + 0.043137254901960784, + 0.0039215686, + 0.0274509804, + 0.0470588235, + 0.047058823529411764, + 0.0039215686, + 0.0274509804, + 0.0509803922, + 0.050980392156862744, + 0.0039215686, + 0.031372549, + 0.0549019608, + 0.054901960784313725, + 0.0039215686, + 0.031372549, + 0.0588235294, + 0.05882352941176471, + 0.0039215686, + 0.0352941176, + 0.062745098, + 0.06274509803921569, + 0.0039215686, + 0.0352941176, + 0.0666666667, + 0.06666666666666667, + 0.0039215686, + 0.0392156863, + 0.0705882353, + 0.07058823529411765, + 0.0039215686, + 0.0392156863, + 0.0745098039, + 0.07450980392156863, + 0.0039215686, + 0.0431372549, + 0.0784313725, + 0.0784313725490196, + 0.0039215686, + 0.0431372549, + 0.0823529412, + 0.08235294117647059, + 0.0039215686, + 0.0470588235, + 0.0862745098, + 0.08627450980392157, + 0.0039215686, + 0.0470588235, + 0.0901960784, + 0.09019607843137255, + 0.0039215686, + 0.0509803922, + 0.0941176471, + 0.09411764705882353, + 0.0039215686, + 0.0509803922, + 0.0980392157, + 0.09803921568627451, + 0.0039215686, + 0.0549019608, + 0.1019607843, + 0.10196078431372549, + 0.0039215686, + 0.0549019608, + 0.1058823529, + 0.10588235294117647, + 0.0039215686, + 0.0588235294, + 0.1098039216, + 0.10980392156862745, + 0.0039215686, + 0.0588235294, + 0.1137254902, + 0.11372549019607843, + 0.0039215686, + 0.062745098, + 0.1176470588, + 0.11764705882352942, + 0.0039215686, + 0.062745098, + 0.1215686275, + 0.12156862745098039, + 0.0039215686, + 0.0666666667, + 0.1254901961, + 0.12549019607843137, + 0.0039215686, + 0.0666666667, + 0.1294117647, + 0.12941176470588237, + 0.0039215686, + 0.0705882353, + 0.1333333333, + 0.13333333333333333, + 0.0039215686, + 0.0705882353, + 0.137254902, + 0.13725490196078433, + 0.0039215686, + 0.0745098039, + 0.1411764706, + 0.1411764705882353, + 0.0039215686, + 0.0745098039, + 0.1450980392, + 0.1450980392156863, + 0.0039215686, + 0.0784313725, + 0.1490196078, + 0.14901960784313725, + 0.0039215686, + 0.0784313725, + 0.1529411765, + 0.15294117647058825, + 0.0039215686, + 0.0823529412, + 0.1568627451, + 0.1568627450980392, + 0.0039215686, + 0.0823529412, + 0.1607843137, + 0.1607843137254902, + 0.0039215686, + 0.0862745098, + 0.1647058824, + 0.16470588235294117, + 0.0039215686, + 0.0862745098, + 0.168627451, + 0.16862745098039217, + 0.0039215686, + 0.0901960784, + 0.1725490196, + 0.17254901960784313, + 0.0039215686, + 0.0901960784, + 0.1764705882, + 0.17647058823529413, + 0.0039215686, + 0.0941176471, + 0.1803921569, + 0.1803921568627451, + 0.0039215686, + 0.0941176471, + 0.1843137255, + 0.1843137254901961, + 0.0039215686, + 0.0980392157, + 0.1882352941, + 0.18823529411764706, + 0.0039215686, + 0.0980392157, + 0.1921568627, + 0.19215686274509805, + 0.0039215686, + 0.1019607843, + 0.1960784314, + 0.19607843137254902, + 0.0039215686, + 0.1019607843, + 0.2, + 0.2, + 0.0039215686, + 0.1058823529, + 0.2039215686, + 0.20392156862745098, + 0.0039215686, + 0.1058823529, + 0.2078431373, + 0.20784313725490197, + 0.0039215686, + 0.1098039216, + 0.2117647059, + 0.21176470588235294, + 0.0039215686, + 0.1098039216, + 0.2156862745, + 0.21568627450980393, + 0.0039215686, + 0.1137254902, + 0.2196078431, + 0.2196078431372549, + 0.0039215686, + 0.1137254902, + 0.2235294118, + 0.2235294117647059, + 0.0039215686, + 0.1176470588, + 0.2274509804, + 0.22745098039215686, + 0.0039215686, + 0.1176470588, + 0.231372549, + 0.23137254901960785, + 0.0039215686, + 0.1215686275, + 0.2352941176, + 0.23529411764705885, + 0.0039215686, + 0.1215686275, + 0.2392156863, + 0.23921568627450984, + 0.0039215686, + 0.1254901961, + 0.2431372549, + 0.24313725490196078, + 0.0039215686, + 0.1254901961, + 0.2470588235, + 0.24705882352941178, + 0.0039215686, + 0.1294117647, + 0.2509803922, + 0.25098039215686274, + 0.0039215686, + 0.1294117647, + 0.2509803922, + 0.2549019607843137, + 0.0078431373, + 0.1254901961, + 0.2549019608, + 0.25882352941176473, + 0.0156862745, + 0.1254901961, + 0.2588235294, + 0.2627450980392157, + 0.0235294118, + 0.1215686275, + 0.262745098, + 0.26666666666666666, + 0.031372549, + 0.1215686275, + 0.2666666667, + 0.27058823529411763, + 0.0392156863, + 0.1176470588, + 0.2705882353, + 0.27450980392156865, + 0.0470588235, + 0.1176470588, + 0.2745098039, + 0.2784313725490196, + 0.0549019608, + 0.1137254902, + 0.2784313725, + 0.2823529411764706, + 0.062745098, + 0.1137254902, + 0.2823529412, + 0.28627450980392155, + 0.0705882353, + 0.1098039216, + 0.2862745098, + 0.2901960784313726, + 0.0784313725, + 0.1098039216, + 0.2901960784, + 0.29411764705882354, + 0.0862745098, + 0.1058823529, + 0.2941176471, + 0.2980392156862745, + 0.0941176471, + 0.1058823529, + 0.2980392157, + 0.30196078431372547, + 0.1019607843, + 0.1019607843, + 0.3019607843, + 0.3058823529411765, + 0.1098039216, + 0.1019607843, + 0.3058823529, + 0.30980392156862746, + 0.1176470588, + 0.0980392157, + 0.3098039216, + 0.3137254901960784, + 0.1254901961, + 0.0980392157, + 0.3137254902, + 0.3176470588235294, + 0.1333333333, + 0.0941176471, + 0.3176470588, + 0.3215686274509804, + 0.1411764706, + 0.0941176471, + 0.3215686275, + 0.3254901960784314, + 0.1490196078, + 0.0901960784, + 0.3254901961, + 0.32941176470588235, + 0.1568627451, + 0.0901960784, + 0.3294117647, + 0.3333333333333333, + 0.1647058824, + 0.0862745098, + 0.3333333333, + 0.33725490196078434, + 0.1725490196, + 0.0862745098, + 0.337254902, + 0.3411764705882353, + 0.1803921569, + 0.0823529412, + 0.3411764706, + 0.34509803921568627, + 0.1882352941, + 0.0823529412, + 0.3450980392, + 0.34901960784313724, + 0.1960784314, + 0.0784313725, + 0.3490196078, + 0.35294117647058826, + 0.2039215686, + 0.0784313725, + 0.3529411765, + 0.3568627450980392, + 0.2117647059, + 0.0745098039, + 0.3568627451, + 0.3607843137254902, + 0.2196078431, + 0.0745098039, + 0.3607843137, + 0.36470588235294116, + 0.2274509804, + 0.0705882353, + 0.3647058824, + 0.3686274509803922, + 0.2352941176, + 0.0705882353, + 0.368627451, + 0.37254901960784315, + 0.2431372549, + 0.0666666667, + 0.3725490196, + 0.3764705882352941, + 0.2509803922, + 0.0666666667, + 0.3764705882, + 0.3803921568627451, + 0.2549019608, + 0.062745098, + 0.3803921569, + 0.3843137254901961, + 0.262745098, + 0.062745098, + 0.3843137255, + 0.38823529411764707, + 0.2705882353, + 0.0588235294, + 0.3882352941, + 0.39215686274509803, + 0.2784313725, + 0.0588235294, + 0.3921568627, + 0.396078431372549, + 0.2862745098, + 0.0549019608, + 0.3960784314, + 0.4, + 0.2941176471, + 0.0549019608, + 0.4, + 0.403921568627451, + 0.3019607843, + 0.0509803922, + 0.4039215686, + 0.40784313725490196, + 0.3098039216, + 0.0509803922, + 0.4078431373, + 0.4117647058823529, + 0.3176470588, + 0.0470588235, + 0.4117647059, + 0.41568627450980394, + 0.3254901961, + 0.0470588235, + 0.4156862745, + 0.4196078431372549, + 0.3333333333, + 0.0431372549, + 0.4196078431, + 0.4235294117647059, + 0.3411764706, + 0.0431372549, + 0.4235294118, + 0.42745098039215684, + 0.3490196078, + 0.0392156863, + 0.4274509804, + 0.43137254901960786, + 0.3568627451, + 0.0392156863, + 0.431372549, + 0.43529411764705883, + 0.3647058824, + 0.0352941176, + 0.4352941176, + 0.4392156862745098, + 0.3725490196, + 0.0352941176, + 0.4392156863, + 0.44313725490196076, + 0.3803921569, + 0.031372549, + 0.4431372549, + 0.4470588235294118, + 0.3882352941, + 0.031372549, + 0.4470588235, + 0.45098039215686275, + 0.3960784314, + 0.0274509804, + 0.4509803922, + 0.4549019607843137, + 0.4039215686, + 0.0274509804, + 0.4549019608, + 0.4588235294117647, + 0.4117647059, + 0.0235294118, + 0.4588235294, + 0.4627450980392157, + 0.4196078431, + 0.0235294118, + 0.462745098, + 0.4666666666666667, + 0.4274509804, + 0.0196078431, + 0.4666666667, + 0.4705882352941177, + 0.4352941176, + 0.0196078431, + 0.4705882353, + 0.4745098039215686, + 0.4431372549, + 0.0156862745, + 0.4745098039, + 0.4784313725490197, + 0.4509803922, + 0.0156862745, + 0.4784313725, + 0.48235294117647065, + 0.4588235294, + 0.0117647059, + 0.4823529412, + 0.48627450980392156, + 0.4666666667, + 0.0117647059, + 0.4862745098, + 0.49019607843137253, + 0.4745098039, + 0.0078431373, + 0.4901960784, + 0.49411764705882355, + 0.4823529412, + 0.0078431373, + 0.4941176471, + 0.4980392156862745, + 0.4901960784, + 0.0039215686, + 0.4980392157, + 0.5019607843137255, + 0.4980392157, + 0.0117647059, + 0.4980392157, + 0.5058823529411764, + 0.5058823529, + 0.0156862745, + 0.4901960784, + 0.5098039215686274, + 0.5137254902, + 0.0235294118, + 0.4823529412, + 0.5137254901960784, + 0.5215686275, + 0.0274509804, + 0.4745098039, + 0.5176470588235295, + 0.5294117647, + 0.0352941176, + 0.4666666667, + 0.5215686274509804, + 0.537254902, + 0.0392156863, + 0.4588235294, + 0.5254901960784314, + 0.5450980392, + 0.0470588235, + 0.4509803922, + 0.5294117647058824, + 0.5529411765, + 0.0509803922, + 0.4431372549, + 0.5333333333333333, + 0.5607843137, + 0.0588235294, + 0.4352941176, + 0.5372549019607843, + 0.568627451, + 0.062745098, + 0.4274509804, + 0.5411764705882353, + 0.5764705882, + 0.0705882353, + 0.4196078431, + 0.5450980392156862, + 0.5843137255, + 0.0745098039, + 0.4117647059, + 0.5490196078431373, + 0.5921568627, + 0.0823529412, + 0.4039215686, + 0.5529411764705883, + 0.6, + 0.0862745098, + 0.3960784314, + 0.5568627450980392, + 0.6078431373, + 0.0941176471, + 0.3882352941, + 0.5607843137254902, + 0.6156862745, + 0.0980392157, + 0.3803921569, + 0.5647058823529412, + 0.6235294118, + 0.1058823529, + 0.3725490196, + 0.5686274509803921, + 0.631372549, + 0.1098039216, + 0.3647058824, + 0.5725490196078431, + 0.6392156863, + 0.1176470588, + 0.3568627451, + 0.5764705882352941, + 0.6470588235, + 0.1215686275, + 0.3490196078, + 0.5803921568627451, + 0.6549019608, + 0.1294117647, + 0.3411764706, + 0.5843137254901961, + 0.662745098, + 0.1333333333, + 0.3333333333, + 0.5882352941176471, + 0.6705882353, + 0.1411764706, + 0.3254901961, + 0.592156862745098, + 0.6784313725, + 0.1450980392, + 0.3176470588, + 0.596078431372549, + 0.6862745098, + 0.1529411765, + 0.3098039216, + 0.6, + 0.6941176471, + 0.1568627451, + 0.3019607843, + 0.6039215686274509, + 0.7019607843, + 0.1647058824, + 0.2941176471, + 0.6078431372549019, + 0.7098039216, + 0.168627451, + 0.2862745098, + 0.611764705882353, + 0.7176470588, + 0.1764705882, + 0.2784313725, + 0.615686274509804, + 0.7254901961, + 0.1803921569, + 0.2705882353, + 0.6196078431372549, + 0.7333333333, + 0.1882352941, + 0.262745098, + 0.6235294117647059, + 0.7411764706, + 0.1921568627, + 0.2549019608, + 0.6274509803921569, + 0.7490196078, + 0.2, + 0.2509803922, + 0.6313725490196078, + 0.7529411765, + 0.2039215686, + 0.2431372549, + 0.6352941176470588, + 0.7607843137, + 0.2117647059, + 0.2352941176, + 0.6392156862745098, + 0.768627451, + 0.2156862745, + 0.2274509804, + 0.6431372549019608, + 0.7764705882, + 0.2235294118, + 0.2196078431, + 0.6470588235294118, + 0.7843137255, + 0.2274509804, + 0.2117647059, + 0.6509803921568628, + 0.7921568627, + 0.2352941176, + 0.2039215686, + 0.6549019607843137, + 0.8, + 0.2392156863, + 0.1960784314, + 0.6588235294117647, + 0.8078431373, + 0.2470588235, + 0.1882352941, + 0.6627450980392157, + 0.8156862745, + 0.2509803922, + 0.1803921569, + 0.6666666666666666, + 0.8235294118, + 0.2549019608, + 0.1725490196, + 0.6705882352941176, + 0.831372549, + 0.2588235294, + 0.1647058824, + 0.6745098039215687, + 0.8392156863, + 0.2666666667, + 0.1568627451, + 0.6784313725490196, + 0.8470588235, + 0.2705882353, + 0.1490196078, + 0.6823529411764706, + 0.8549019608, + 0.2784313725, + 0.1411764706, + 0.6862745098039216, + 0.862745098, + 0.2823529412, + 0.1333333333, + 0.6901960784313725, + 0.8705882353, + 0.2901960784, + 0.1254901961, + 0.6941176470588235, + 0.8784313725, + 0.2941176471, + 0.1176470588, + 0.6980392156862745, + 0.8862745098, + 0.3019607843, + 0.1098039216, + 0.7019607843137254, + 0.8941176471, + 0.3058823529, + 0.1019607843, + 0.7058823529411765, + 0.9019607843, + 0.3137254902, + 0.0941176471, + 0.7098039215686275, + 0.9098039216, + 0.3176470588, + 0.0862745098, + 0.7137254901960784, + 0.9176470588, + 0.3254901961, + 0.0784313725, + 0.7176470588235294, + 0.9254901961, + 0.3294117647, + 0.0705882353, + 0.7215686274509804, + 0.9333333333, + 0.337254902, + 0.062745098, + 0.7254901960784313, + 0.9411764706, + 0.3411764706, + 0.0549019608, + 0.7294117647058823, + 0.9490196078, + 0.3490196078, + 0.0470588235, + 0.7333333333333333, + 0.9568627451, + 0.3529411765, + 0.0392156863, + 0.7372549019607844, + 0.9647058824, + 0.3607843137, + 0.031372549, + 0.7411764705882353, + 0.9725490196, + 0.3647058824, + 0.0235294118, + 0.7450980392156863, + 0.9803921569, + 0.3725490196, + 0.0156862745, + 0.7490196078431373, + 0.9882352941, + 0.3725490196, + 0.0039215686, + 0.7529411764705882, + 0.9960784314, + 0.3843137255, + 0.0156862745, + 0.7568627450980392, + 0.9960784314, + 0.3921568627, + 0.031372549, + 0.7607843137254902, + 0.9960784314, + 0.4039215686, + 0.0470588235, + 0.7647058823529411, + 0.9960784314, + 0.4117647059, + 0.062745098, + 0.7686274509803922, + 0.9960784314, + 0.4235294118, + 0.0784313725, + 0.7725490196078432, + 0.9960784314, + 0.431372549, + 0.0941176471, + 0.7764705882352941, + 0.9960784314, + 0.4431372549, + 0.1098039216, + 0.7803921568627451, + 0.9960784314, + 0.4509803922, + 0.1254901961, + 0.7843137254901961, + 0.9960784314, + 0.462745098, + 0.1411764706, + 0.788235294117647, + 0.9960784314, + 0.4705882353, + 0.1568627451, + 0.792156862745098, + 0.9960784314, + 0.4823529412, + 0.1725490196, + 0.796078431372549, + 0.9960784314, + 0.4901960784, + 0.1882352941, + 0.8, + 0.9960784314, + 0.5019607843, + 0.2039215686, + 0.803921568627451, + 0.9960784314, + 0.5098039216, + 0.2196078431, + 0.807843137254902, + 0.9960784314, + 0.5215686275, + 0.2352941176, + 0.8117647058823529, + 0.9960784314, + 0.5294117647, + 0.2509803922, + 0.8156862745098039, + 0.9960784314, + 0.5411764706, + 0.262745098, + 0.8196078431372549, + 0.9960784314, + 0.5490196078, + 0.2784313725, + 0.8235294117647058, + 0.9960784314, + 0.5607843137, + 0.2941176471, + 0.8274509803921568, + 0.9960784314, + 0.568627451, + 0.3098039216, + 0.8313725490196079, + 0.9960784314, + 0.5803921569, + 0.3254901961, + 0.8352941176470589, + 0.9960784314, + 0.5882352941, + 0.3411764706, + 0.8392156862745098, + 0.9960784314, + 0.6, + 0.3568627451, + 0.8431372549019608, + 0.9960784314, + 0.6078431373, + 0.3725490196, + 0.8470588235294118, + 0.9960784314, + 0.6196078431, + 0.3882352941, + 0.8509803921568627, + 0.9960784314, + 0.6274509804, + 0.4039215686, + 0.8549019607843137, + 0.9960784314, + 0.6392156863, + 0.4196078431, + 0.8588235294117647, + 0.9960784314, + 0.6470588235, + 0.4352941176, + 0.8627450980392157, + 0.9960784314, + 0.6588235294, + 0.4509803922, + 0.8666666666666667, + 0.9960784314, + 0.6666666667, + 0.4666666667, + 0.8705882352941177, + 0.9960784314, + 0.6784313725, + 0.4823529412, + 0.8745098039215686, + 0.9960784314, + 0.6862745098, + 0.4980392157, + 0.8784313725490196, + 0.9960784314, + 0.6980392157, + 0.5137254902, + 0.8823529411764706, + 0.9960784314, + 0.7058823529, + 0.5294117647, + 0.8862745098039215, + 0.9960784314, + 0.7176470588, + 0.5450980392, + 0.8901960784313725, + 0.9960784314, + 0.7254901961, + 0.5607843137, + 0.8941176470588236, + 0.9960784314, + 0.737254902, + 0.5764705882, + 0.8980392156862745, + 0.9960784314, + 0.7450980392, + 0.5921568627, + 0.9019607843137255, + 0.9960784314, + 0.7529411765, + 0.6078431373, + 0.9058823529411765, + 0.9960784314, + 0.7607843137, + 0.6235294118, + 0.9098039215686274, + 0.9960784314, + 0.7725490196, + 0.6392156863, + 0.9137254901960784, + 0.9960784314, + 0.7803921569, + 0.6549019608, + 0.9176470588235294, + 0.9960784314, + 0.7921568627, + 0.6705882353, + 0.9215686274509803, + 0.9960784314, + 0.8, + 0.6862745098, + 0.9254901960784314, + 0.9960784314, + 0.8117647059, + 0.7019607843, + 0.9294117647058824, + 0.9960784314, + 0.8196078431, + 0.7176470588, + 0.9333333333333333, + 0.9960784314, + 0.831372549, + 0.7333333333, + 0.9372549019607843, + 0.9960784314, + 0.8392156863, + 0.7490196078, + 0.9411764705882354, + 0.9960784314, + 0.8509803922, + 0.7607843137, + 0.9450980392156864, + 0.9960784314, + 0.8588235294, + 0.7764705882, + 0.9490196078431372, + 0.9960784314, + 0.8705882353, + 0.7921568627, + 0.9529411764705882, + 0.9960784314, + 0.8784313725, + 0.8078431373, + 0.9568627450980394, + 0.9960784314, + 0.8901960784, + 0.8235294118, + 0.9607843137254903, + 0.9960784314, + 0.8980392157, + 0.8392156863, + 0.9647058823529413, + 0.9960784314, + 0.9098039216, + 0.8549019608, + 0.9686274509803922, + 0.9960784314, + 0.9176470588, + 0.8705882353, + 0.9725490196078431, + 0.9960784314, + 0.9294117647, + 0.8862745098, + 0.9764705882352941, + 0.9960784314, + 0.937254902, + 0.9019607843, + 0.9803921568627451, + 0.9960784314, + 0.9490196078, + 0.9176470588, + 0.984313725490196, + 0.9960784314, + 0.9568627451, + 0.9333333333, + 0.9882352941176471, + 0.9960784314, + 0.968627451, + 0.9490196078, + 0.9921568627450981, + 0.9960784314, + 0.9764705882, + 0.9647058824, + 0.996078431372549, + 0.9960784314, + 0.9882352941, + 0.9803921569, + 1.0, + 0.9960784314, + 0.9882352941, + 0.9803921569, + ], + }, + { + ColorSpace: 'RGB', + Name: 'ge', + RGBPoints: [ + 0.0, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.00392156862745098, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.00784313725490196, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.011764705882352941, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.01568627450980392, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.0196078431372549, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.023529411764705882, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.027450980392156862, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.03137254901960784, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.03529411764705882, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.0392156862745098, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.043137254901960784, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.047058823529411764, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.050980392156862744, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.054901960784313725, + 0.0078431373, + 0.0078431373, + 0.0078431373, + 0.05882352941176471, + 0.0117647059, + 0.0078431373, + 0.0078431373, + 0.06274509803921569, + 0.0078431373, + 0.0156862745, + 0.0156862745, + 0.06666666666666667, + 0.0078431373, + 0.0235294118, + 0.0235294118, + 0.07058823529411765, + 0.0078431373, + 0.031372549, + 0.031372549, + 0.07450980392156863, + 0.0078431373, + 0.0392156863, + 0.0392156863, + 0.0784313725490196, + 0.0078431373, + 0.0470588235, + 0.0470588235, + 0.08235294117647059, + 0.0078431373, + 0.0549019608, + 0.0549019608, + 0.08627450980392157, + 0.0078431373, + 0.062745098, + 0.062745098, + 0.09019607843137255, + 0.0078431373, + 0.0705882353, + 0.0705882353, + 0.09411764705882353, + 0.0078431373, + 0.0784313725, + 0.0784313725, + 0.09803921568627451, + 0.0078431373, + 0.0901960784, + 0.0862745098, + 0.10196078431372549, + 0.0078431373, + 0.0980392157, + 0.0941176471, + 0.10588235294117647, + 0.0078431373, + 0.1058823529, + 0.1019607843, + 0.10980392156862745, + 0.0078431373, + 0.1137254902, + 0.1098039216, + 0.11372549019607843, + 0.0078431373, + 0.1215686275, + 0.1176470588, + 0.11764705882352942, + 0.0078431373, + 0.1294117647, + 0.1254901961, + 0.12156862745098039, + 0.0078431373, + 0.137254902, + 0.1333333333, + 0.12549019607843137, + 0.0078431373, + 0.1450980392, + 0.1411764706, + 0.12941176470588237, + 0.0078431373, + 0.1529411765, + 0.1490196078, + 0.13333333333333333, + 0.0078431373, + 0.1647058824, + 0.1568627451, + 0.13725490196078433, + 0.0078431373, + 0.1725490196, + 0.1647058824, + 0.1411764705882353, + 0.0078431373, + 0.1803921569, + 0.1725490196, + 0.1450980392156863, + 0.0078431373, + 0.1882352941, + 0.1803921569, + 0.14901960784313725, + 0.0078431373, + 0.1960784314, + 0.1882352941, + 0.15294117647058825, + 0.0078431373, + 0.2039215686, + 0.1960784314, + 0.1568627450980392, + 0.0078431373, + 0.2117647059, + 0.2039215686, + 0.1607843137254902, + 0.0078431373, + 0.2196078431, + 0.2117647059, + 0.16470588235294117, + 0.0078431373, + 0.2274509804, + 0.2196078431, + 0.16862745098039217, + 0.0078431373, + 0.2352941176, + 0.2274509804, + 0.17254901960784313, + 0.0078431373, + 0.2470588235, + 0.2352941176, + 0.17647058823529413, + 0.0078431373, + 0.2509803922, + 0.2431372549, + 0.1803921568627451, + 0.0078431373, + 0.2549019608, + 0.2509803922, + 0.1843137254901961, + 0.0078431373, + 0.262745098, + 0.2509803922, + 0.18823529411764706, + 0.0078431373, + 0.2705882353, + 0.2588235294, + 0.19215686274509805, + 0.0078431373, + 0.2784313725, + 0.2666666667, + 0.19607843137254902, + 0.0078431373, + 0.2862745098, + 0.2745098039, + 0.2, + 0.0078431373, + 0.2941176471, + 0.2823529412, + 0.20392156862745098, + 0.0078431373, + 0.3019607843, + 0.2901960784, + 0.20784313725490197, + 0.0078431373, + 0.3137254902, + 0.2980392157, + 0.21176470588235294, + 0.0078431373, + 0.3215686275, + 0.3058823529, + 0.21568627450980393, + 0.0078431373, + 0.3294117647, + 0.3137254902, + 0.2196078431372549, + 0.0078431373, + 0.337254902, + 0.3215686275, + 0.2235294117647059, + 0.0078431373, + 0.3450980392, + 0.3294117647, + 0.22745098039215686, + 0.0078431373, + 0.3529411765, + 0.337254902, + 0.23137254901960785, + 0.0078431373, + 0.3607843137, + 0.3450980392, + 0.23529411764705885, + 0.0078431373, + 0.368627451, + 0.3529411765, + 0.23921568627450984, + 0.0078431373, + 0.3764705882, + 0.3607843137, + 0.24313725490196078, + 0.0078431373, + 0.3843137255, + 0.368627451, + 0.24705882352941178, + 0.0078431373, + 0.3960784314, + 0.3764705882, + 0.25098039215686274, + 0.0078431373, + 0.4039215686, + 0.3843137255, + 0.2549019607843137, + 0.0078431373, + 0.4117647059, + 0.3921568627, + 0.25882352941176473, + 0.0078431373, + 0.4196078431, + 0.4, + 0.2627450980392157, + 0.0078431373, + 0.4274509804, + 0.4078431373, + 0.26666666666666666, + 0.0078431373, + 0.4352941176, + 0.4156862745, + 0.27058823529411763, + 0.0078431373, + 0.4431372549, + 0.4235294118, + 0.27450980392156865, + 0.0078431373, + 0.4509803922, + 0.431372549, + 0.2784313725490196, + 0.0078431373, + 0.4588235294, + 0.4392156863, + 0.2823529411764706, + 0.0078431373, + 0.4705882353, + 0.4470588235, + 0.28627450980392155, + 0.0078431373, + 0.4784313725, + 0.4549019608, + 0.2901960784313726, + 0.0078431373, + 0.4862745098, + 0.462745098, + 0.29411764705882354, + 0.0078431373, + 0.4941176471, + 0.4705882353, + 0.2980392156862745, + 0.0078431373, + 0.5019607843, + 0.4784313725, + 0.30196078431372547, + 0.0117647059, + 0.5098039216, + 0.4862745098, + 0.3058823529411765, + 0.0196078431, + 0.5019607843, + 0.4941176471, + 0.30980392156862746, + 0.0274509804, + 0.4941176471, + 0.5058823529, + 0.3137254901960784, + 0.0352941176, + 0.4862745098, + 0.5137254902, + 0.3176470588235294, + 0.0431372549, + 0.4784313725, + 0.5215686275, + 0.3215686274509804, + 0.0509803922, + 0.4705882353, + 0.5294117647, + 0.3254901960784314, + 0.0588235294, + 0.462745098, + 0.537254902, + 0.32941176470588235, + 0.0666666667, + 0.4549019608, + 0.5450980392, + 0.3333333333333333, + 0.0745098039, + 0.4470588235, + 0.5529411765, + 0.33725490196078434, + 0.0823529412, + 0.4392156863, + 0.5607843137, + 0.3411764705882353, + 0.0901960784, + 0.431372549, + 0.568627451, + 0.34509803921568627, + 0.0980392157, + 0.4235294118, + 0.5764705882, + 0.34901960784313724, + 0.1058823529, + 0.4156862745, + 0.5843137255, + 0.35294117647058826, + 0.1137254902, + 0.4078431373, + 0.5921568627, + 0.3568627450980392, + 0.1215686275, + 0.4, + 0.6, + 0.3607843137254902, + 0.1294117647, + 0.3921568627, + 0.6078431373, + 0.36470588235294116, + 0.137254902, + 0.3843137255, + 0.6156862745, + 0.3686274509803922, + 0.1450980392, + 0.3764705882, + 0.6235294118, + 0.37254901960784315, + 0.1529411765, + 0.368627451, + 0.631372549, + 0.3764705882352941, + 0.1607843137, + 0.3607843137, + 0.6392156863, + 0.3803921568627451, + 0.168627451, + 0.3529411765, + 0.6470588235, + 0.3843137254901961, + 0.1764705882, + 0.3450980392, + 0.6549019608, + 0.38823529411764707, + 0.1843137255, + 0.337254902, + 0.662745098, + 0.39215686274509803, + 0.1921568627, + 0.3294117647, + 0.6705882353, + 0.396078431372549, + 0.2, + 0.3215686275, + 0.6784313725, + 0.4, + 0.2078431373, + 0.3137254902, + 0.6862745098, + 0.403921568627451, + 0.2156862745, + 0.3058823529, + 0.6941176471, + 0.40784313725490196, + 0.2235294118, + 0.2980392157, + 0.7019607843, + 0.4117647058823529, + 0.231372549, + 0.2901960784, + 0.7098039216, + 0.41568627450980394, + 0.2392156863, + 0.2823529412, + 0.7176470588, + 0.4196078431372549, + 0.2470588235, + 0.2745098039, + 0.7254901961, + 0.4235294117647059, + 0.2509803922, + 0.2666666667, + 0.7333333333, + 0.42745098039215684, + 0.2509803922, + 0.2588235294, + 0.7411764706, + 0.43137254901960786, + 0.2588235294, + 0.2509803922, + 0.7490196078, + 0.43529411764705883, + 0.2666666667, + 0.2509803922, + 0.7490196078, + 0.4392156862745098, + 0.2745098039, + 0.2431372549, + 0.7568627451, + 0.44313725490196076, + 0.2823529412, + 0.2352941176, + 0.7647058824, + 0.4470588235294118, + 0.2901960784, + 0.2274509804, + 0.7725490196, + 0.45098039215686275, + 0.2980392157, + 0.2196078431, + 0.7803921569, + 0.4549019607843137, + 0.3058823529, + 0.2117647059, + 0.7882352941, + 0.4588235294117647, + 0.3137254902, + 0.2039215686, + 0.7960784314, + 0.4627450980392157, + 0.3215686275, + 0.1960784314, + 0.8039215686, + 0.4666666666666667, + 0.3294117647, + 0.1882352941, + 0.8117647059, + 0.4705882352941177, + 0.337254902, + 0.1803921569, + 0.8196078431, + 0.4745098039215686, + 0.3450980392, + 0.1725490196, + 0.8274509804, + 0.4784313725490197, + 0.3529411765, + 0.1647058824, + 0.8352941176, + 0.48235294117647065, + 0.3607843137, + 0.1568627451, + 0.8431372549, + 0.48627450980392156, + 0.368627451, + 0.1490196078, + 0.8509803922, + 0.49019607843137253, + 0.3764705882, + 0.1411764706, + 0.8588235294, + 0.49411764705882355, + 0.3843137255, + 0.1333333333, + 0.8666666667, + 0.4980392156862745, + 0.3921568627, + 0.1254901961, + 0.8745098039, + 0.5019607843137255, + 0.4, + 0.1176470588, + 0.8823529412, + 0.5058823529411764, + 0.4078431373, + 0.1098039216, + 0.8901960784, + 0.5098039215686274, + 0.4156862745, + 0.1019607843, + 0.8980392157, + 0.5137254901960784, + 0.4235294118, + 0.0941176471, + 0.9058823529, + 0.5176470588235295, + 0.431372549, + 0.0862745098, + 0.9137254902, + 0.5215686274509804, + 0.4392156863, + 0.0784313725, + 0.9215686275, + 0.5254901960784314, + 0.4470588235, + 0.0705882353, + 0.9294117647, + 0.5294117647058824, + 0.4549019608, + 0.062745098, + 0.937254902, + 0.5333333333333333, + 0.462745098, + 0.0549019608, + 0.9450980392, + 0.5372549019607843, + 0.4705882353, + 0.0470588235, + 0.9529411765, + 0.5411764705882353, + 0.4784313725, + 0.0392156863, + 0.9607843137, + 0.5450980392156862, + 0.4862745098, + 0.031372549, + 0.968627451, + 0.5490196078431373, + 0.4941176471, + 0.0235294118, + 0.9764705882, + 0.5529411764705883, + 0.4980392157, + 0.0156862745, + 0.9843137255, + 0.5568627450980392, + 0.5058823529, + 0.0078431373, + 0.9921568627, + 0.5607843137254902, + 0.5137254902, + 0.0156862745, + 0.9803921569, + 0.5647058823529412, + 0.5215686275, + 0.0235294118, + 0.9647058824, + 0.5686274509803921, + 0.5294117647, + 0.0352941176, + 0.9490196078, + 0.5725490196078431, + 0.537254902, + 0.0431372549, + 0.9333333333, + 0.5764705882352941, + 0.5450980392, + 0.0509803922, + 0.9176470588, + 0.5803921568627451, + 0.5529411765, + 0.062745098, + 0.9019607843, + 0.5843137254901961, + 0.5607843137, + 0.0705882353, + 0.8862745098, + 0.5882352941176471, + 0.568627451, + 0.0784313725, + 0.8705882353, + 0.592156862745098, + 0.5764705882, + 0.0901960784, + 0.8549019608, + 0.596078431372549, + 0.5843137255, + 0.0980392157, + 0.8392156863, + 0.6, + 0.5921568627, + 0.1098039216, + 0.8235294118, + 0.6039215686274509, + 0.6, + 0.1176470588, + 0.8078431373, + 0.6078431372549019, + 0.6078431373, + 0.1254901961, + 0.7921568627, + 0.611764705882353, + 0.6156862745, + 0.137254902, + 0.7764705882, + 0.615686274509804, + 0.6235294118, + 0.1450980392, + 0.7607843137, + 0.6196078431372549, + 0.631372549, + 0.1529411765, + 0.7490196078, + 0.6235294117647059, + 0.6392156863, + 0.1647058824, + 0.737254902, + 0.6274509803921569, + 0.6470588235, + 0.1725490196, + 0.7215686275, + 0.6313725490196078, + 0.6549019608, + 0.1843137255, + 0.7058823529, + 0.6352941176470588, + 0.662745098, + 0.1921568627, + 0.6901960784, + 0.6392156862745098, + 0.6705882353, + 0.2, + 0.6745098039, + 0.6431372549019608, + 0.6784313725, + 0.2117647059, + 0.6588235294, + 0.6470588235294118, + 0.6862745098, + 0.2196078431, + 0.6431372549, + 0.6509803921568628, + 0.6941176471, + 0.2274509804, + 0.6274509804, + 0.6549019607843137, + 0.7019607843, + 0.2392156863, + 0.6117647059, + 0.6588235294117647, + 0.7098039216, + 0.2470588235, + 0.5960784314, + 0.6627450980392157, + 0.7176470588, + 0.2509803922, + 0.5803921569, + 0.6666666666666666, + 0.7254901961, + 0.2588235294, + 0.5647058824, + 0.6705882352941176, + 0.7333333333, + 0.2666666667, + 0.5490196078, + 0.6745098039215687, + 0.7411764706, + 0.2784313725, + 0.5333333333, + 0.6784313725490196, + 0.7490196078, + 0.2862745098, + 0.5176470588, + 0.6823529411764706, + 0.7490196078, + 0.2941176471, + 0.5019607843, + 0.6862745098039216, + 0.7529411765, + 0.3058823529, + 0.4862745098, + 0.6901960784313725, + 0.7607843137, + 0.3137254902, + 0.4705882353, + 0.6941176470588235, + 0.768627451, + 0.3215686275, + 0.4549019608, + 0.6980392156862745, + 0.7764705882, + 0.3333333333, + 0.4392156863, + 0.7019607843137254, + 0.7843137255, + 0.3411764706, + 0.4235294118, + 0.7058823529411765, + 0.7921568627, + 0.3529411765, + 0.4078431373, + 0.7098039215686275, + 0.8, + 0.3607843137, + 0.3921568627, + 0.7137254901960784, + 0.8078431373, + 0.368627451, + 0.3764705882, + 0.7176470588235294, + 0.8156862745, + 0.3803921569, + 0.3607843137, + 0.7215686274509804, + 0.8235294118, + 0.3882352941, + 0.3450980392, + 0.7254901960784313, + 0.831372549, + 0.3960784314, + 0.3294117647, + 0.7294117647058823, + 0.8392156863, + 0.4078431373, + 0.3137254902, + 0.7333333333333333, + 0.8470588235, + 0.4156862745, + 0.2980392157, + 0.7372549019607844, + 0.8549019608, + 0.4274509804, + 0.2823529412, + 0.7411764705882353, + 0.862745098, + 0.4352941176, + 0.2666666667, + 0.7450980392156863, + 0.8705882353, + 0.4431372549, + 0.2509803922, + 0.7490196078431373, + 0.8784313725, + 0.4549019608, + 0.2431372549, + 0.7529411764705882, + 0.8862745098, + 0.462745098, + 0.2274509804, + 0.7568627450980392, + 0.8941176471, + 0.4705882353, + 0.2117647059, + 0.7607843137254902, + 0.9019607843, + 0.4823529412, + 0.1960784314, + 0.7647058823529411, + 0.9098039216, + 0.4901960784, + 0.1803921569, + 0.7686274509803922, + 0.9176470588, + 0.4980392157, + 0.1647058824, + 0.7725490196078432, + 0.9254901961, + 0.5098039216, + 0.1490196078, + 0.7764705882352941, + 0.9333333333, + 0.5176470588, + 0.1333333333, + 0.7803921568627451, + 0.9411764706, + 0.5294117647, + 0.1176470588, + 0.7843137254901961, + 0.9490196078, + 0.537254902, + 0.1019607843, + 0.788235294117647, + 0.9568627451, + 0.5450980392, + 0.0862745098, + 0.792156862745098, + 0.9647058824, + 0.5568627451, + 0.0705882353, + 0.796078431372549, + 0.9725490196, + 0.5647058824, + 0.0549019608, + 0.8, + 0.9803921569, + 0.5725490196, + 0.0392156863, + 0.803921568627451, + 0.9882352941, + 0.5843137255, + 0.0235294118, + 0.807843137254902, + 0.9921568627, + 0.5921568627, + 0.0078431373, + 0.8117647058823529, + 0.9921568627, + 0.6039215686, + 0.0274509804, + 0.8156862745098039, + 0.9921568627, + 0.6117647059, + 0.0509803922, + 0.8196078431372549, + 0.9921568627, + 0.6196078431, + 0.0745098039, + 0.8235294117647058, + 0.9921568627, + 0.631372549, + 0.0980392157, + 0.8274509803921568, + 0.9921568627, + 0.6392156863, + 0.1215686275, + 0.8313725490196079, + 0.9921568627, + 0.6470588235, + 0.1411764706, + 0.8352941176470589, + 0.9921568627, + 0.6588235294, + 0.1647058824, + 0.8392156862745098, + 0.9921568627, + 0.6666666667, + 0.1882352941, + 0.8431372549019608, + 0.9921568627, + 0.6784313725, + 0.2117647059, + 0.8470588235294118, + 0.9921568627, + 0.6862745098, + 0.2352941176, + 0.8509803921568627, + 0.9921568627, + 0.6941176471, + 0.2509803922, + 0.8549019607843137, + 0.9921568627, + 0.7058823529, + 0.2705882353, + 0.8588235294117647, + 0.9921568627, + 0.7137254902, + 0.2941176471, + 0.8627450980392157, + 0.9921568627, + 0.7215686275, + 0.3176470588, + 0.8666666666666667, + 0.9921568627, + 0.7333333333, + 0.3411764706, + 0.8705882352941177, + 0.9921568627, + 0.7411764706, + 0.3647058824, + 0.8745098039215686, + 0.9921568627, + 0.7490196078, + 0.3843137255, + 0.8784313725490196, + 0.9921568627, + 0.7529411765, + 0.4078431373, + 0.8823529411764706, + 0.9921568627, + 0.7607843137, + 0.431372549, + 0.8862745098039215, + 0.9921568627, + 0.7725490196, + 0.4549019608, + 0.8901960784313725, + 0.9921568627, + 0.7803921569, + 0.4784313725, + 0.8941176470588236, + 0.9921568627, + 0.7882352941, + 0.4980392157, + 0.8980392156862745, + 0.9921568627, + 0.8, + 0.5215686275, + 0.9019607843137255, + 0.9921568627, + 0.8078431373, + 0.5450980392, + 0.9058823529411765, + 0.9921568627, + 0.8156862745, + 0.568627451, + 0.9098039215686274, + 0.9921568627, + 0.8274509804, + 0.5921568627, + 0.9137254901960784, + 0.9921568627, + 0.8352941176, + 0.6156862745, + 0.9176470588235294, + 0.9921568627, + 0.8470588235, + 0.6352941176, + 0.9215686274509803, + 0.9921568627, + 0.8549019608, + 0.6588235294, + 0.9254901960784314, + 0.9921568627, + 0.862745098, + 0.6823529412, + 0.9294117647058824, + 0.9921568627, + 0.8745098039, + 0.7058823529, + 0.9333333333333333, + 0.9921568627, + 0.8823529412, + 0.7294117647, + 0.9372549019607843, + 0.9921568627, + 0.8901960784, + 0.7490196078, + 0.9411764705882354, + 0.9921568627, + 0.9019607843, + 0.7647058824, + 0.9450980392156864, + 0.9921568627, + 0.9098039216, + 0.7882352941, + 0.9490196078431372, + 0.9921568627, + 0.9215686275, + 0.8117647059, + 0.9529411764705882, + 0.9921568627, + 0.9294117647, + 0.8352941176, + 0.9568627450980394, + 0.9921568627, + 0.937254902, + 0.8588235294, + 0.9607843137254903, + 0.9921568627, + 0.9490196078, + 0.8784313725, + 0.9647058823529413, + 0.9921568627, + 0.9568627451, + 0.9019607843, + 0.9686274509803922, + 0.9921568627, + 0.9647058824, + 0.9254901961, + 0.9725490196078431, + 0.9921568627, + 0.9764705882, + 0.9490196078, + 0.9764705882352941, + 0.9921568627, + 0.9843137255, + 0.9725490196, + 0.9803921568627451, + 0.9921568627, + 0.9921568627, + 0.9921568627, + 0.984313725490196, + 0.9921568627, + 0.9921568627, + 0.9921568627, + 0.9882352941176471, + 0.9921568627, + 0.9921568627, + 0.9921568627, + 0.9921568627450981, + 0.9921568627, + 0.9921568627, + 0.9921568627, + 0.996078431372549, + 0.9921568627, + 0.9921568627, + 0.9921568627, + 1.0, + 0.9921568627, + 0.9921568627, + 0.9921568627, + ], + }, + { + ColorSpace: 'RGB', + Name: 'siemens', + RGBPoints: [ + 0.0, + 0.0078431373, + 0.0039215686, + 0.1254901961, + 0.00392156862745098, + 0.0078431373, + 0.0039215686, + 0.1254901961, + 0.00784313725490196, + 0.0078431373, + 0.0039215686, + 0.1882352941, + 0.011764705882352941, + 0.0117647059, + 0.0039215686, + 0.2509803922, + 0.01568627450980392, + 0.0117647059, + 0.0039215686, + 0.3098039216, + 0.0196078431372549, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.023529411764705882, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.027450980392156862, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.03137254901960784, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.03529411764705882, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.0392156862745098, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.043137254901960784, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.047058823529411764, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.050980392156862744, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.054901960784313725, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.05882352941176471, + 0.0156862745, + 0.0039215686, + 0.3725490196, + 0.06274509803921569, + 0.0156862745, + 0.0039215686, + 0.3882352941, + 0.06666666666666667, + 0.0156862745, + 0.0039215686, + 0.4078431373, + 0.07058823529411765, + 0.0156862745, + 0.0039215686, + 0.4235294118, + 0.07450980392156863, + 0.0156862745, + 0.0039215686, + 0.4431372549, + 0.0784313725490196, + 0.0156862745, + 0.0039215686, + 0.462745098, + 0.08235294117647059, + 0.0156862745, + 0.0039215686, + 0.4784313725, + 0.08627450980392157, + 0.0156862745, + 0.0039215686, + 0.4980392157, + 0.09019607843137255, + 0.0196078431, + 0.0039215686, + 0.5137254902, + 0.09411764705882353, + 0.0196078431, + 0.0039215686, + 0.5333333333, + 0.09803921568627451, + 0.0196078431, + 0.0039215686, + 0.5529411765, + 0.10196078431372549, + 0.0196078431, + 0.0039215686, + 0.568627451, + 0.10588235294117647, + 0.0196078431, + 0.0039215686, + 0.5882352941, + 0.10980392156862745, + 0.0196078431, + 0.0039215686, + 0.6039215686, + 0.11372549019607843, + 0.0196078431, + 0.0039215686, + 0.6235294118, + 0.11764705882352942, + 0.0196078431, + 0.0039215686, + 0.6431372549, + 0.12156862745098039, + 0.0235294118, + 0.0039215686, + 0.6588235294, + 0.12549019607843137, + 0.0235294118, + 0.0039215686, + 0.6784313725, + 0.12941176470588237, + 0.0235294118, + 0.0039215686, + 0.6980392157, + 0.13333333333333333, + 0.0235294118, + 0.0039215686, + 0.7137254902, + 0.13725490196078433, + 0.0235294118, + 0.0039215686, + 0.7333333333, + 0.1411764705882353, + 0.0235294118, + 0.0039215686, + 0.7490196078, + 0.1450980392156863, + 0.0235294118, + 0.0039215686, + 0.7647058824, + 0.14901960784313725, + 0.0235294118, + 0.0039215686, + 0.7843137255, + 0.15294117647058825, + 0.0274509804, + 0.0039215686, + 0.8, + 0.1568627450980392, + 0.0274509804, + 0.0039215686, + 0.8196078431, + 0.1607843137254902, + 0.0274509804, + 0.0039215686, + 0.8352941176, + 0.16470588235294117, + 0.0274509804, + 0.0039215686, + 0.8549019608, + 0.16862745098039217, + 0.0274509804, + 0.0039215686, + 0.8745098039, + 0.17254901960784313, + 0.0274509804, + 0.0039215686, + 0.8901960784, + 0.17647058823529413, + 0.0274509804, + 0.0039215686, + 0.9098039216, + 0.1803921568627451, + 0.031372549, + 0.0039215686, + 0.9294117647, + 0.1843137254901961, + 0.031372549, + 0.0039215686, + 0.9254901961, + 0.18823529411764706, + 0.0509803922, + 0.0039215686, + 0.9098039216, + 0.19215686274509805, + 0.0705882353, + 0.0039215686, + 0.8901960784, + 0.19607843137254902, + 0.0901960784, + 0.0039215686, + 0.8705882353, + 0.2, + 0.1137254902, + 0.0039215686, + 0.8509803922, + 0.20392156862745098, + 0.1333333333, + 0.0039215686, + 0.831372549, + 0.20784313725490197, + 0.1529411765, + 0.0039215686, + 0.8117647059, + 0.21176470588235294, + 0.1725490196, + 0.0039215686, + 0.7921568627, + 0.21568627450980393, + 0.1960784314, + 0.0039215686, + 0.7725490196, + 0.2196078431372549, + 0.2156862745, + 0.0039215686, + 0.7529411765, + 0.2235294117647059, + 0.2352941176, + 0.0039215686, + 0.737254902, + 0.22745098039215686, + 0.2509803922, + 0.0039215686, + 0.7176470588, + 0.23137254901960785, + 0.2745098039, + 0.0039215686, + 0.6980392157, + 0.23529411764705885, + 0.2941176471, + 0.0039215686, + 0.6784313725, + 0.23921568627450984, + 0.3137254902, + 0.0039215686, + 0.6588235294, + 0.24313725490196078, + 0.3333333333, + 0.0039215686, + 0.6392156863, + 0.24705882352941178, + 0.3568627451, + 0.0039215686, + 0.6196078431, + 0.25098039215686274, + 0.3764705882, + 0.0039215686, + 0.6, + 0.2549019607843137, + 0.3960784314, + 0.0039215686, + 0.5803921569, + 0.25882352941176473, + 0.4156862745, + 0.0039215686, + 0.5607843137, + 0.2627450980392157, + 0.4392156863, + 0.0039215686, + 0.5411764706, + 0.26666666666666666, + 0.4588235294, + 0.0039215686, + 0.5215686275, + 0.27058823529411763, + 0.4784313725, + 0.0039215686, + 0.5019607843, + 0.27450980392156865, + 0.4980392157, + 0.0039215686, + 0.4823529412, + 0.2784313725490196, + 0.5215686275, + 0.0039215686, + 0.4666666667, + 0.2823529411764706, + 0.5411764706, + 0.0039215686, + 0.4470588235, + 0.28627450980392155, + 0.5607843137, + 0.0039215686, + 0.4274509804, + 0.2901960784313726, + 0.5803921569, + 0.0039215686, + 0.4078431373, + 0.29411764705882354, + 0.6039215686, + 0.0039215686, + 0.3882352941, + 0.2980392156862745, + 0.6235294118, + 0.0039215686, + 0.368627451, + 0.30196078431372547, + 0.6431372549, + 0.0039215686, + 0.3490196078, + 0.3058823529411765, + 0.662745098, + 0.0039215686, + 0.3294117647, + 0.30980392156862746, + 0.6862745098, + 0.0039215686, + 0.3098039216, + 0.3137254901960784, + 0.7058823529, + 0.0039215686, + 0.2901960784, + 0.3176470588235294, + 0.7254901961, + 0.0039215686, + 0.2705882353, + 0.3215686274509804, + 0.7450980392, + 0.0039215686, + 0.2509803922, + 0.3254901960784314, + 0.7647058824, + 0.0039215686, + 0.2352941176, + 0.32941176470588235, + 0.7843137255, + 0.0039215686, + 0.2156862745, + 0.3333333333333333, + 0.8039215686, + 0.0039215686, + 0.1960784314, + 0.33725490196078434, + 0.8235294118, + 0.0039215686, + 0.1764705882, + 0.3411764705882353, + 0.8470588235, + 0.0039215686, + 0.1568627451, + 0.34509803921568627, + 0.8666666667, + 0.0039215686, + 0.137254902, + 0.34901960784313724, + 0.8862745098, + 0.0039215686, + 0.1176470588, + 0.35294117647058826, + 0.9058823529, + 0.0039215686, + 0.0980392157, + 0.3568627450980392, + 0.9294117647, + 0.0039215686, + 0.0784313725, + 0.3607843137254902, + 0.9490196078, + 0.0039215686, + 0.0588235294, + 0.36470588235294116, + 0.968627451, + 0.0039215686, + 0.0392156863, + 0.3686274509803922, + 0.9921568627, + 0.0039215686, + 0.0235294118, + 0.37254901960784315, + 0.9529411765, + 0.0039215686, + 0.0588235294, + 0.3764705882352941, + 0.9529411765, + 0.0078431373, + 0.0549019608, + 0.3803921568627451, + 0.9529411765, + 0.0156862745, + 0.0549019608, + 0.3843137254901961, + 0.9529411765, + 0.0235294118, + 0.0549019608, + 0.38823529411764707, + 0.9529411765, + 0.031372549, + 0.0549019608, + 0.39215686274509803, + 0.9529411765, + 0.0352941176, + 0.0549019608, + 0.396078431372549, + 0.9529411765, + 0.0431372549, + 0.0549019608, + 0.4, + 0.9529411765, + 0.0509803922, + 0.0549019608, + 0.403921568627451, + 0.9529411765, + 0.0588235294, + 0.0549019608, + 0.40784313725490196, + 0.9529411765, + 0.062745098, + 0.0549019608, + 0.4117647058823529, + 0.9529411765, + 0.0705882353, + 0.0549019608, + 0.41568627450980394, + 0.9529411765, + 0.0784313725, + 0.0509803922, + 0.4196078431372549, + 0.9529411765, + 0.0862745098, + 0.0509803922, + 0.4235294117647059, + 0.9568627451, + 0.0941176471, + 0.0509803922, + 0.42745098039215684, + 0.9568627451, + 0.0980392157, + 0.0509803922, + 0.43137254901960786, + 0.9568627451, + 0.1058823529, + 0.0509803922, + 0.43529411764705883, + 0.9568627451, + 0.1137254902, + 0.0509803922, + 0.4392156862745098, + 0.9568627451, + 0.1215686275, + 0.0509803922, + 0.44313725490196076, + 0.9568627451, + 0.1254901961, + 0.0509803922, + 0.4470588235294118, + 0.9568627451, + 0.1333333333, + 0.0509803922, + 0.45098039215686275, + 0.9568627451, + 0.1411764706, + 0.0509803922, + 0.4549019607843137, + 0.9568627451, + 0.1490196078, + 0.0470588235, + 0.4588235294117647, + 0.9568627451, + 0.1568627451, + 0.0470588235, + 0.4627450980392157, + 0.9568627451, + 0.1607843137, + 0.0470588235, + 0.4666666666666667, + 0.9568627451, + 0.168627451, + 0.0470588235, + 0.4705882352941177, + 0.9607843137, + 0.1764705882, + 0.0470588235, + 0.4745098039215686, + 0.9607843137, + 0.1843137255, + 0.0470588235, + 0.4784313725490197, + 0.9607843137, + 0.1882352941, + 0.0470588235, + 0.48235294117647065, + 0.9607843137, + 0.1960784314, + 0.0470588235, + 0.48627450980392156, + 0.9607843137, + 0.2039215686, + 0.0470588235, + 0.49019607843137253, + 0.9607843137, + 0.2117647059, + 0.0470588235, + 0.49411764705882355, + 0.9607843137, + 0.2196078431, + 0.0431372549, + 0.4980392156862745, + 0.9607843137, + 0.2235294118, + 0.0431372549, + 0.5019607843137255, + 0.9607843137, + 0.231372549, + 0.0431372549, + 0.5058823529411764, + 0.9607843137, + 0.2392156863, + 0.0431372549, + 0.5098039215686274, + 0.9607843137, + 0.2470588235, + 0.0431372549, + 0.5137254901960784, + 0.9607843137, + 0.2509803922, + 0.0431372549, + 0.5176470588235295, + 0.9647058824, + 0.2549019608, + 0.0431372549, + 0.5215686274509804, + 0.9647058824, + 0.262745098, + 0.0431372549, + 0.5254901960784314, + 0.9647058824, + 0.2705882353, + 0.0431372549, + 0.5294117647058824, + 0.9647058824, + 0.2745098039, + 0.0431372549, + 0.5333333333333333, + 0.9647058824, + 0.2823529412, + 0.0392156863, + 0.5372549019607843, + 0.9647058824, + 0.2901960784, + 0.0392156863, + 0.5411764705882353, + 0.9647058824, + 0.2980392157, + 0.0392156863, + 0.5450980392156862, + 0.9647058824, + 0.3058823529, + 0.0392156863, + 0.5490196078431373, + 0.9647058824, + 0.3098039216, + 0.0392156863, + 0.5529411764705883, + 0.9647058824, + 0.3176470588, + 0.0392156863, + 0.5568627450980392, + 0.9647058824, + 0.3254901961, + 0.0392156863, + 0.5607843137254902, + 0.9647058824, + 0.3333333333, + 0.0392156863, + 0.5647058823529412, + 0.9647058824, + 0.337254902, + 0.0392156863, + 0.5686274509803921, + 0.968627451, + 0.3450980392, + 0.0392156863, + 0.5725490196078431, + 0.968627451, + 0.3529411765, + 0.0352941176, + 0.5764705882352941, + 0.968627451, + 0.3607843137, + 0.0352941176, + 0.5803921568627451, + 0.968627451, + 0.368627451, + 0.0352941176, + 0.5843137254901961, + 0.968627451, + 0.3725490196, + 0.0352941176, + 0.5882352941176471, + 0.968627451, + 0.3803921569, + 0.0352941176, + 0.592156862745098, + 0.968627451, + 0.3882352941, + 0.0352941176, + 0.596078431372549, + 0.968627451, + 0.3960784314, + 0.0352941176, + 0.6, + 0.968627451, + 0.4, + 0.0352941176, + 0.6039215686274509, + 0.968627451, + 0.4078431373, + 0.0352941176, + 0.6078431372549019, + 0.968627451, + 0.4156862745, + 0.0352941176, + 0.611764705882353, + 0.968627451, + 0.4235294118, + 0.031372549, + 0.615686274509804, + 0.9725490196, + 0.431372549, + 0.031372549, + 0.6196078431372549, + 0.9725490196, + 0.4352941176, + 0.031372549, + 0.6235294117647059, + 0.9725490196, + 0.4431372549, + 0.031372549, + 0.6274509803921569, + 0.9725490196, + 0.4509803922, + 0.031372549, + 0.6313725490196078, + 0.9725490196, + 0.4588235294, + 0.031372549, + 0.6352941176470588, + 0.9725490196, + 0.462745098, + 0.031372549, + 0.6392156862745098, + 0.9725490196, + 0.4705882353, + 0.031372549, + 0.6431372549019608, + 0.9725490196, + 0.4784313725, + 0.031372549, + 0.6470588235294118, + 0.9725490196, + 0.4862745098, + 0.031372549, + 0.6509803921568628, + 0.9725490196, + 0.4941176471, + 0.0274509804, + 0.6549019607843137, + 0.9725490196, + 0.4980392157, + 0.0274509804, + 0.6588235294117647, + 0.9725490196, + 0.5058823529, + 0.0274509804, + 0.6627450980392157, + 0.9764705882, + 0.5137254902, + 0.0274509804, + 0.6666666666666666, + 0.9764705882, + 0.5215686275, + 0.0274509804, + 0.6705882352941176, + 0.9764705882, + 0.5254901961, + 0.0274509804, + 0.6745098039215687, + 0.9764705882, + 0.5333333333, + 0.0274509804, + 0.6784313725490196, + 0.9764705882, + 0.5411764706, + 0.0274509804, + 0.6823529411764706, + 0.9764705882, + 0.5490196078, + 0.0274509804, + 0.6862745098039216, + 0.9764705882, + 0.5529411765, + 0.0274509804, + 0.6901960784313725, + 0.9764705882, + 0.5607843137, + 0.0235294118, + 0.6941176470588235, + 0.9764705882, + 0.568627451, + 0.0235294118, + 0.6980392156862745, + 0.9764705882, + 0.5764705882, + 0.0235294118, + 0.7019607843137254, + 0.9764705882, + 0.5843137255, + 0.0235294118, + 0.7058823529411765, + 0.9764705882, + 0.5882352941, + 0.0235294118, + 0.7098039215686275, + 0.9764705882, + 0.5960784314, + 0.0235294118, + 0.7137254901960784, + 0.9803921569, + 0.6039215686, + 0.0235294118, + 0.7176470588235294, + 0.9803921569, + 0.6117647059, + 0.0235294118, + 0.7215686274509804, + 0.9803921569, + 0.6156862745, + 0.0235294118, + 0.7254901960784313, + 0.9803921569, + 0.6235294118, + 0.0235294118, + 0.7294117647058823, + 0.9803921569, + 0.631372549, + 0.0196078431, + 0.7333333333333333, + 0.9803921569, + 0.6392156863, + 0.0196078431, + 0.7372549019607844, + 0.9803921569, + 0.6470588235, + 0.0196078431, + 0.7411764705882353, + 0.9803921569, + 0.6509803922, + 0.0196078431, + 0.7450980392156863, + 0.9803921569, + 0.6588235294, + 0.0196078431, + 0.7490196078431373, + 0.9803921569, + 0.6666666667, + 0.0196078431, + 0.7529411764705882, + 0.9803921569, + 0.6745098039, + 0.0196078431, + 0.7568627450980392, + 0.9803921569, + 0.6784313725, + 0.0196078431, + 0.7607843137254902, + 0.9843137255, + 0.6862745098, + 0.0196078431, + 0.7647058823529411, + 0.9843137255, + 0.6941176471, + 0.0196078431, + 0.7686274509803922, + 0.9843137255, + 0.7019607843, + 0.0156862745, + 0.7725490196078432, + 0.9843137255, + 0.7098039216, + 0.0156862745, + 0.7764705882352941, + 0.9843137255, + 0.7137254902, + 0.0156862745, + 0.7803921568627451, + 0.9843137255, + 0.7215686275, + 0.0156862745, + 0.7843137254901961, + 0.9843137255, + 0.7294117647, + 0.0156862745, + 0.788235294117647, + 0.9843137255, + 0.737254902, + 0.0156862745, + 0.792156862745098, + 0.9843137255, + 0.7411764706, + 0.0156862745, + 0.796078431372549, + 0.9843137255, + 0.7490196078, + 0.0156862745, + 0.8, + 0.9843137255, + 0.7529411765, + 0.0156862745, + 0.803921568627451, + 0.9843137255, + 0.7607843137, + 0.0156862745, + 0.807843137254902, + 0.9882352941, + 0.768627451, + 0.0156862745, + 0.8117647058823529, + 0.9882352941, + 0.768627451, + 0.0156862745, + 0.8156862745098039, + 0.9843137255, + 0.7843137255, + 0.0117647059, + 0.8196078431372549, + 0.9843137255, + 0.8, + 0.0117647059, + 0.8235294117647058, + 0.9843137255, + 0.8156862745, + 0.0117647059, + 0.8274509803921568, + 0.9803921569, + 0.831372549, + 0.0117647059, + 0.8313725490196079, + 0.9803921569, + 0.8431372549, + 0.0117647059, + 0.8352941176470589, + 0.9803921569, + 0.8588235294, + 0.0078431373, + 0.8392156862745098, + 0.9803921569, + 0.8745098039, + 0.0078431373, + 0.8431372549019608, + 0.9764705882, + 0.8901960784, + 0.0078431373, + 0.8470588235294118, + 0.9764705882, + 0.9058823529, + 0.0078431373, + 0.8509803921568627, + 0.9764705882, + 0.9176470588, + 0.0078431373, + 0.8549019607843137, + 0.9764705882, + 0.9333333333, + 0.0039215686, + 0.8588235294117647, + 0.9725490196, + 0.9490196078, + 0.0039215686, + 0.8627450980392157, + 0.9725490196, + 0.9647058824, + 0.0039215686, + 0.8666666666666667, + 0.9725490196, + 0.9803921569, + 0.0039215686, + 0.8705882352941177, + 0.9725490196, + 0.9960784314, + 0.0039215686, + 0.8745098039215686, + 0.9725490196, + 0.9960784314, + 0.0039215686, + 0.8784313725490196, + 0.9725490196, + 0.9960784314, + 0.0352941176, + 0.8823529411764706, + 0.9725490196, + 0.9960784314, + 0.0666666667, + 0.8862745098039215, + 0.9725490196, + 0.9960784314, + 0.0980392157, + 0.8901960784313725, + 0.9725490196, + 0.9960784314, + 0.1294117647, + 0.8941176470588236, + 0.9725490196, + 0.9960784314, + 0.1647058824, + 0.8980392156862745, + 0.9764705882, + 0.9960784314, + 0.1960784314, + 0.9019607843137255, + 0.9764705882, + 0.9960784314, + 0.2274509804, + 0.9058823529411765, + 0.9764705882, + 0.9960784314, + 0.2549019608, + 0.9098039215686274, + 0.9764705882, + 0.9960784314, + 0.2901960784, + 0.9137254901960784, + 0.9764705882, + 0.9960784314, + 0.3215686275, + 0.9176470588235294, + 0.9803921569, + 0.9960784314, + 0.3529411765, + 0.9215686274509803, + 0.9803921569, + 0.9960784314, + 0.3843137255, + 0.9254901960784314, + 0.9803921569, + 0.9960784314, + 0.4156862745, + 0.9294117647058824, + 0.9803921569, + 0.9960784314, + 0.4509803922, + 0.9333333333333333, + 0.9803921569, + 0.9960784314, + 0.4823529412, + 0.9372549019607843, + 0.9843137255, + 0.9960784314, + 0.5137254902, + 0.9411764705882354, + 0.9843137255, + 0.9960784314, + 0.5450980392, + 0.9450980392156864, + 0.9843137255, + 0.9960784314, + 0.5803921569, + 0.9490196078431372, + 0.9843137255, + 0.9960784314, + 0.6117647059, + 0.9529411764705882, + 0.9843137255, + 0.9960784314, + 0.6431372549, + 0.9568627450980394, + 0.9882352941, + 0.9960784314, + 0.6745098039, + 0.9607843137254903, + 0.9882352941, + 0.9960784314, + 0.7058823529, + 0.9647058823529413, + 0.9882352941, + 0.9960784314, + 0.7411764706, + 0.9686274509803922, + 0.9882352941, + 0.9960784314, + 0.768627451, + 0.9725490196078431, + 0.9882352941, + 0.9960784314, + 0.8, + 0.9764705882352941, + 0.9921568627, + 0.9960784314, + 0.831372549, + 0.9803921568627451, + 0.9921568627, + 0.9960784314, + 0.8666666667, + 0.984313725490196, + 0.9921568627, + 0.9960784314, + 0.8980392157, + 0.9882352941176471, + 0.9921568627, + 0.9960784314, + 0.9294117647, + 0.9921568627450981, + 0.9921568627, + 0.9960784314, + 0.9607843137, + 0.996078431372549, + 0.9960784314, + 0.9960784314, + 0.9607843137, + 1.0, + 0.9960784314, + 0.9960784314, + 0.9607843137, + ], + }, +]; diff --git a/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js b/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js new file mode 100644 index 00000000000..996ebd0f50f --- /dev/null +++ b/extensions/tmtv/src/utils/createAndDownloadTMTVReport.js @@ -0,0 +1,50 @@ +export default function createAndDownloadTMTVReport( + segReport, + additionalReportRows +) { + const firstReport = segReport[Object.keys(segReport)[0]]; + const columns = Object.keys(firstReport); + const csv = [columns.join(',')]; + + Object.values(segReport).forEach(segmentation => { + const row = []; + columns.forEach(column => { + // if it is array then we need to replace , with space to avoid csv parsing error + row.push( + Array.isArray(segmentation[column]) + ? segmentation[column].join(' ') + : segmentation[column] + ); + }); + csv.push(row.join(',')); + }); + + csv.push(''); + csv.push(''); + csv.push(''); + + csv.push(`Patient ID,${firstReport.PatientID}`); + csv.push(`Study Date,${firstReport.StudyDate}`); + csv.push(''); + additionalReportRows.forEach(({ key, value: values }) => { + const temp = []; + temp.push(`${key}`); + Object.keys(values).forEach(k => { + temp.push(`${k}`); + temp.push(`${values[k]}`); + }); + + csv.push(temp.join(',')); + }); + + const blob = new Blob([csv.join('\n')], { + type: 'text/csv;charset=utf-8', + }); + + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = `${firstReport.PatientID}_tmtv.csv`; + a.click(); +} diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js new file mode 100644 index 00000000000..147b0cbf606 --- /dev/null +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js @@ -0,0 +1,262 @@ +import AnnotationToPointData from './measurements/AnnotationToPointData'; +import dcmjs from 'dcmjs'; +import { DicomMetadataStore } from '@ohif/core'; + +const { DicomMetaDictionary } = dcmjs.data; + +export default class RTSSReport { + constructor() {} + + /** + * Convert handles to RTSSReport report object containing the dcmjs dicom dataset. + * + * Note: The tool data needs to be formatted in a specific way, and currently + * it is limited to the RectangleROIStartEndTool in the Cornerstone. + * + * @param annotations Array of Cornerstone tool annotation data + * @param metadataProvider Metadata provider + * @param options report generation options + * @returns Report object containing the dataset + */ + static generateReport(annotations, metadataProvider, options) { + let dataset = initializeDataset(annotations, metadataProvider); + + annotations.forEach((annotation, index) => { + const ContourSequence = AnnotationToPointData.convert( + annotation, + index, + metadataProvider, + options + ); + + dataset.StructureSetROISequence.push( + getStructureSetModule(annotation, index, metadataProvider) + ); + + dataset.ROIContourSequence.push(ContourSequence); + dataset.RTROIObservationsSequence.push( + getRTROIObservationsSequence(annotation, index, metadataProvider) + ); + + // ReferencedSeriesSequence + // Todo: handle more than one series + dataset.ReferencedSeriesSequence = getReferencedSeriesSequence( + annotation, + index, + metadataProvider + ); + + // ReferencedFrameOfReferenceSequence + dataset.ReferencedFrameOfReferenceSequence = getReferencedFrameOfReferenceSequence( + annotation, + metadataProvider, + dataset + ); + }); + + const fileMetaInformationVersionArray = new Uint8Array(2); + fileMetaInformationVersionArray[1] = 1; + + const _meta = { + FileMetaInformationVersion: { + Value: [fileMetaInformationVersionArray.buffer], + vr: 'OB', + }, + TransferSyntaxUID: { + Value: ['1.2.840.10008.1.2.1'], + vr: 'UI', + }, + ImplementationClassUID: { + Value: [DicomMetaDictionary.uid()], // TODO: could be git hash or other valid id + vr: 'UI', + }, + ImplementationVersionName: { + Value: ['dcmjs'], + vr: 'SH', + }, + }; + + dataset._meta = _meta; + + return dataset; + } + + /** + * Generate Cornerstone tool state from dataset + * @param {object} dataset dataset + * @param {object} hooks + * @param {function} hooks.getToolClass Function to map dataset to a tool class + * @returns + */ + static generateToolState(dataset, hooks = {}) { + // Todo + console.warn('RTSSReport.generateToolState not implemented'); + } +} + +function initializeDataset(annotations, metadataProvider) { + const rtSOPInstanceUID = DicomMetaDictionary.uid(); + + // get the first annotation data + const { + referencedImageId: imageId, + FrameOfReferenceUID, + } = annotations[0].metadata; + + const { studyInstanceUID } = metadataProvider.get( + 'generalSeriesModule', + imageId + ); + + const patientModule = getPatientModule(imageId, metadataProvider); + const rtSeriesModule = getRTSeriesModule(imageId, metadataProvider); + + return { + StructureSetROISequence: [], + ROIContourSequence: [], + RTROIObservationsSequence: [], + ReferencedSeriesSequence: [], + ReferencedFrameOfReferenceSequence: [], + ...patientModule, + ...rtSeriesModule, + StudyInstanceUID: studyInstanceUID, + SOPClassUID: '1.2.840.10008.5.1.4.1.1.481.3', // RT Structure Set Storage + SOPInstanceUID: rtSOPInstanceUID, + Manufacturer: 'dcmjs', + Modality: 'RTSTRUCT', + FrameOfReferenceUID, + PositionReferenceIndicator: '', + StructureSetLabel: '', + StructureSetName: '', + ReferringPhysicianName: '', + OperatorsName: '', + StructureSetDate: DicomMetaDictionary.date(), + StructureSetTime: DicomMetaDictionary.time(), + }; +} + +function getPatientModule(imageId, metadataProvider) { + const generalSeriesModule = metadataProvider.get( + 'generalSeriesModule', + imageId + ); + const generalStudyModule = metadataProvider.get( + 'generalStudyModule', + imageId + ); + const patientStudyModule = metadataProvider.get( + 'patientStudyModule', + imageId + ); + const patientModule = metadataProvider.get('patientModule', imageId); + const patientDemographicModule = metadataProvider.get( + 'patientDemographicModule', + imageId + ); + + return { + Modality: generalSeriesModule.modality, + PatientID: patientModule.patientId, + PatientName: patientModule.patientName, + PatientBirthDate: '', + PatientAge: patientStudyModule.patientAge, + PatientSex: patientDemographicModule.patientSex, + PatientWeight: patientStudyModule.patientWeight, + StudyDate: generalStudyModule.studyDate, + StudyTime: generalStudyModule.studyTime, + StudyID: 'ToDo', + AccessionNumber: generalStudyModule.accessionNumber, + }; +} + +function getReferencedFrameOfReferenceSequence( + toolData, + metadataProvider, + dataset +) { + const { referencedImageId: imageId, FrameOfReferenceUID } = toolData.metadata; + const instance = metadataProvider.get('instance', imageId); + const { SeriesInstanceUID } = instance; + + const { ReferencedSeriesSequence } = dataset; + + return [ + { + FrameOfReferenceUID, + RTReferencedStudySequence: [ + { + ReferencedSOPClassUID: dataset.SOPClassUID, + ReferencedSOPInstanceUID: dataset.SOPInstanceUID, + RTReferencedSeriesSequence: [ + { + SeriesInstanceUID, + ContourImageSequence: [ + ...ReferencedSeriesSequence[0].ReferencedInstanceSequence, + ], + }, + ], + }, + ], + }, + ]; +} + +function getReferencedSeriesSequence(toolData, index, metadataProvider) { + // grab imageId from toolData + const { referencedImageId: imageId } = toolData.metadata; + const instance = metadataProvider.get('instance', imageId); + const { SeriesInstanceUID, StudyInstanceUID } = instance; + + const ReferencedSeriesSequence = []; + if (SeriesInstanceUID) { + const series = DicomMetadataStore.getSeries( + StudyInstanceUID, + SeriesInstanceUID + ); + + const ReferencedSeries = { + SeriesInstanceUID, + ReferencedInstanceSequence: [], + }; + + series.instances.forEach(instance => { + const { SOPInstanceUID, SOPClassUID } = instance; + ReferencedSeries.ReferencedInstanceSequence.push({ + ReferencedSOPClassUID: SOPClassUID, + ReferencedSOPInstanceUID: SOPInstanceUID, + }); + }); + + ReferencedSeriesSequence.push(ReferencedSeries); + } + + return ReferencedSeriesSequence; +} + +function getRTSeriesModule(imageId, metadataProvider) { + return { + SeriesInstanceUID: DicomMetaDictionary.uid(), // generate a new series instance uid + SeriesNumber: '99', // Todo:: what should be the series number? + }; +} + +function getStructureSetModule(toolData, index, metadataProvider) { + const { FrameOfReferenceUID } = toolData.metadata; + + return { + ROINumber: index + 1, + ROIName: `Todo: name ${index + 1}`, + ROIDescription: `Todo: description ${index + 1}`, + ROIGenerationAlgorithm: 'Todo: algorithm', + ReferencedFrameOfReferenceUID: FrameOfReferenceUID, + }; +} + +function getRTROIObservationsSequence(toolData, index, metadataProvider) { + return { + ObservationNumber: index + 1, + ReferencedROINumber: index + 1, + RTROIInterpretedType: 'Todo: type', + ROIInterpreter: 'Todo: interpreter', + }; +} diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js new file mode 100644 index 00000000000..999b37addaf --- /dev/null +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js @@ -0,0 +1,15 @@ +import RTSSReport from './RTSSReport'; +import dcmjs from 'dcmjs'; +import { classes } from '@ohif/core'; + +const { datasetToBlob } = dcmjs.data; +const metadataProvider = classes.MetadataProvider; + +export default function dicomRTAnnotationExport(annotations) { + const dataset = RTSSReport.generateReport(annotations, metadataProvider); + const reportBlob = datasetToBlob(dataset); + + //Create a URL for the binary. + var objectUrl = URL.createObjectURL(reportBlob); + window.location.assign(objectUrl); +} diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/index.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/index.js new file mode 100644 index 00000000000..7a915da2ce9 --- /dev/null +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/index.js @@ -0,0 +1,3 @@ +import dicomRTAnnotationExport from './dicomRTAnnotationExport'; + +export default dicomRTAnnotationExport; diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js new file mode 100644 index 00000000000..f74fdc72ae7 --- /dev/null +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js @@ -0,0 +1,58 @@ +import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold'; + +function validateAnnotation(annotation) { + if (!annotation?.data) { + throw new Error('Tool data is empty'); + } + + if (!annotation.metadata || annotation.metadata.referenceImageId) { + throw new Error('Tool data is not associated with any imageId'); + } +} + +class AnnotationToPointData { + constructor() {} + + static convert(annotation, index, metadataProvider) { + validateAnnotation(annotation); + + const { toolName } = annotation.metadata; + const toolClass = AnnotationToPointData.TOOL_NAMES[toolName]; + + if (!toolClass) { + throw new Error( + `Unknown tool type: ${toolName}, cannot convert to RTSSReport` + ); + } + + // Each toolData should become a list of contours, ContourSequence + // contains a list of contours with their pointData, their geometry + // type and their length. + const ContourSequence = toolClass.getContourSequence( + annotation, + metadataProvider + ); + + // Todo: random rgb color for now, options should be passed in + const color = [ + Math.floor(Math.random() * 255), + Math.floor(Math.random() * 255), + Math.floor(Math.random() * 255), + ]; + + return { + ReferencedROINumber: index + 1, + ROIDisplayColor: color, + ContourSequence, + }; + } + + static register(toolClass) { + AnnotationToPointData.TOOL_NAMES[toolClass.toolName] = toolClass; + } +} + +AnnotationToPointData.TOOL_NAMES = {}; +AnnotationToPointData.register(RectangleROIStartEndThreshold); + +export default AnnotationToPointData; diff --git a/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js new file mode 100644 index 00000000000..803bef365d0 --- /dev/null +++ b/extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js @@ -0,0 +1,56 @@ +// comment +class RectangleROIStartEndThreshold { + constructor() {} + + static getContourSequence(toolData, metadataProvider) { + const { data } = toolData; + const { projectionPoints, projectionPointsImageIds } = data.cachedStats; + + return projectionPoints.map((point, index) => { + const ContourData = getPointData(point); + const ContourImageSequence = getContourImageSequence( + projectionPointsImageIds[index], + metadataProvider + ); + + return { + NumberOfContourPoints: ContourData.length / 3, + ContourImageSequence, + ContourGeometricType: 'CLOSED_PLANAR', + ContourData, + }; + }); + } +} + +RectangleROIStartEndThreshold.toolName = 'RectangleROIStartEndThreshold'; + +function getPointData(points) { + // Since this is a closed contour, the order of the points is important. + // re-order the points to be in the correct order clockwise + // Spread to make sure Float32Arrays are converted to arrays + const orderedPoints = [ + ...points[0], + ...points[1], + ...points[3], + ...points[2], + ]; + const pointsArray = orderedPoints.flat(); + + // reduce the precision of the points to 2 decimal places + const pointsArrayWithPrecision = pointsArray.map(point => { + return point.toFixed(2); + }); + + return pointsArrayWithPrecision; +} + +function getContourImageSequence(imageId, metadataProvider) { + const sopCommon = metadataProvider.get('sopCommonModule', imageId); + + return { + ReferencedSOPClassUID: sopCommon.sopClassUID, + ReferencedSOPInstanceUID: sopCommon.sopInstanceUID, + }; +} +export default RectangleROIStartEndThreshold; diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getStudiesForPatientByStudyInstanceUID.js b/extensions/tmtv/src/utils/getStudiesForPatientByStudyInstanceUID.js similarity index 100% rename from extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/getStudiesForPatientByStudyInstanceUID.js rename to extensions/tmtv/src/utils/getStudiesForPatientByStudyInstanceUID.js diff --git a/extensions/tmtv/src/utils/getThresholdValue.ts b/extensions/tmtv/src/utils/getThresholdValue.ts new file mode 100644 index 00000000000..3f597433bd6 --- /dev/null +++ b/extensions/tmtv/src/utils/getThresholdValue.ts @@ -0,0 +1,66 @@ +import * as csTools from '@cornerstonejs/tools'; + +function getThresholdValues( + annotationUIDs, + referencedVolume, + config +): { lower: number; upper: number } { + if (config.strategy === 'range') { + return { + lower: Number(config.lower), + upper: Number(config.upper), + }; + } + + // roiStats + const { weight } = config; + const { imageData } = referencedVolume; + const values = imageData + .getPointData() + .getScalars() + .getData(); + + // Todo: add support for other strategies + const { fn, baseValue } = _getStrategyFn('max'); + let value = baseValue; + + const annotations = annotationUIDs.map(annotationUID => + csTools.annotation.state.getAnnotation(annotationUID) + ); + + const boundsIJK = csTools.utilities.rectangleROITool.getBoundsIJKFromRectangleAnnotations( + annotations, + referencedVolume + ); + + const [[iMin, iMax], [jMin, jMax], [kMin, kMax]] = boundsIJK; + + for (let i = iMin; i <= iMax; i++) { + for (let j = jMin; j <= jMax; j++) { + for (let k = kMin; k <= kMax; k++) { + const offset = imageData.computeOffsetIndex([i, j, k]); + value = fn(values[offset], value); + } + } + } + + return { + lower: weight * value, + upper: +Infinity, + }; +} + +function _getStrategyFn( + statistic +): { fn: (a: number, b: number) => number; baseValue: number } { + const baseValue = -Infinity; + const fn = (number, maxValue) => { + if (number > maxValue) { + maxValue = number; + } + return maxValue; + }; + return { fn, baseValue }; +} + +export default getThresholdValues; diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js b/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js new file mode 100644 index 00000000000..9508c253285 --- /dev/null +++ b/extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js @@ -0,0 +1,75 @@ +import SUPPORTED_TOOLS from './constants/supportedTools'; +import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; + +const RectangleROIStartEndThreshold = { + toAnnotation: (measurement, definition) => {}, + + /** + * Maps cornerstone annotation event data to measurement service format. + * + * @param {Object} cornerstone Cornerstone event data + * @return {Measurement} Measurement instance + */ + toMeasurement: ( + csToolsEventDetail, + DisplaySetService, + CornerstoneViewportService + ) => { + const { annotation, viewportId } = csToolsEventDetail; + const { metadata, data, annotationUID } = annotation; + + if (!metadata || !data) { + console.warn('Length tool: Missing metadata or data'); + return null; + } + + const { toolName, referencedImageId, FrameOfReferenceUID } = metadata; + const validToolType = SUPPORTED_TOOLS.includes(toolName); + + if (!validToolType) { + throw new Error('Tool not supported'); + } + + const { + SOPInstanceUID, + SeriesInstanceUID, + StudyInstanceUID, + } = getSOPInstanceAttributes( + referencedImageId, + CornerstoneViewportService, + viewportId + ); + + let displaySet; + + if (SOPInstanceUID) { + displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + } else { + displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); + } + + const { cachedStats } = data; + + return { + uid: annotationUID, + SOPInstanceUID, + FrameOfReferenceUID, + // points, + metadata, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + toolName: metadata.toolName, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: metadata.label, + // displayText: displayText, + data: data.cachedStats, + type: 'RectangleROIStartEndThreshold', + // getReport, + }; + }, +}; + +export default RectangleROIStartEndThreshold; diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/constants/supportedTools.js b/extensions/tmtv/src/utils/measurementServiceMappings/constants/supportedTools.js new file mode 100644 index 00000000000..31cc1059ef7 --- /dev/null +++ b/extensions/tmtv/src/utils/measurementServiceMappings/constants/supportedTools.js @@ -0,0 +1 @@ +export default ['RectangleROIStartEndThreshold']; diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js b/extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js new file mode 100644 index 00000000000..ec69721ef28 --- /dev/null +++ b/extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js @@ -0,0 +1,26 @@ +import RectangleROIStartEndThreshold from './RectangleROIStartEndThreshold'; + +const measurementServiceMappingsFactory = ( + MeasurementService, + DisplaySetService, + CornerstoneViewportService +) => { + return { + RectangleROIStartEndThreshold: { + toAnnotation: RectangleROIStartEndThreshold.toAnnotation, + toMeasurement: csToolsAnnotation => + RectangleROIStartEndThreshold.toMeasurement( + csToolsAnnotation, + DisplaySetService, + CornerstoneViewportService + ), + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.ROI_THRESHOLD_MANUAL, + }, + ], + }, + }; +}; + +export default measurementServiceMappingsFactory; diff --git a/extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js b/extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js new file mode 100644 index 00000000000..4f412417822 --- /dev/null +++ b/extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js @@ -0,0 +1,17 @@ +import { metaData } from '@cornerstonejs/core'; + +export default function getSOPInstanceAttributes(imageId) { + if (imageId) { + return _getUIDFromImageID(imageId); + } +} + +function _getUIDFromImageID(imageId) { + const instance = metaData.get('instance', imageId); + + return { + SOPInstanceUID: instance.SOPInstanceUID, + SeriesInstanceUID: instance.SeriesInstanceUID, + StudyInstanceUID: instance.StudyInstanceUID, + }; +} diff --git a/jest.config.base.js b/jest.config.base.js index 6e5ee2a01f1..b9c22e2711c 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -8,11 +8,11 @@ module.exports = { roots: ['/src'], transform: { '^.+\\.js$': 'babel-jest', - '^.+\\.jsx$': 'babel-jest' + '^.+\\.jsx$': 'babel-jest', }, testMatch: ['/src/**/*.test.js'], testPathIgnorePatterns: ['/node_modules/'], - moduleFileExtensions: ['js', 'jsx'], + moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], moduleNameMapper: { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/__mocks__/fileMock.js', diff --git a/modes/basic-dev-mode/.webpack/webpack.dev.js b/modes/basic-dev-mode/.webpack/webpack.dev.js new file mode 100644 index 00000000000..1ae30844802 --- /dev/null +++ b/modes/basic-dev-mode/.webpack/webpack.dev.js @@ -0,0 +1,8 @@ +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + return webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); +}; diff --git a/modes/basic-dev-mode/.webpack/webpack.prod.js b/modes/basic-dev-mode/.webpack/webpack.prod.js new file mode 100644 index 00000000000..ed60094742b --- /dev/null +++ b/modes/basic-dev-mode/.webpack/webpack.prod.js @@ -0,0 +1,43 @@ +const webpack = require('webpack'); +const merge = require('webpack-merge'); +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const pkg = require('./../package.json'); + +const ROOT_DIR = path.join(__dirname, './..'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); + + return merge(commonConfig, { + stats: { + colors: true, + hash: true, + timings: true, + assets: true, + chunks: false, + chunkModules: false, + modules: false, + children: false, + warnings: true, + }, + optimization: { + minimize: true, + sideEffects: true, + }, + output: { + path: ROOT_DIR, + library: 'OHIFModeLongitudinal', + libraryTarget: 'umd', + libraryExport: 'default', + filename: pkg.main, + }, + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + ], + }); +}; diff --git a/modes/basic-dev-mode/LICENSE b/modes/basic-dev-mode/LICENSE new file mode 100644 index 00000000000..19e20dd35ca --- /dev/null +++ b/modes/basic-dev-mode/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Open Health Imaging Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modes/basic-dev-mode/babel.config.js b/modes/basic-dev-mode/babel.config.js new file mode 100644 index 00000000000..325ca2a8ee7 --- /dev/null +++ b/modes/basic-dev-mode/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/modes/basic-dev-mode/package.json b/modes/basic-dev-mode/package.json new file mode 100644 index 00000000000..a0f52c22b75 --- /dev/null +++ b/modes/basic-dev-mode/package.json @@ -0,0 +1,47 @@ +{ + "name": "@ohif/mode-basic-dev-mode", + "version": "3.0.0", + "description": "Basic OHIF Viewer Using Cornerstone", + "author": "OHIF", + "license": "MIT", + "repository": "OHIF/Viewers", + "main": "dist/index.umd.js", + "module": "src/index.js", + "engines": { + "node": ">=10", + "npm": ">=6", + "yarn": ">=1.16.0" + }, + "files": [ + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo", + "dev:cornerstone": "yarn run dev", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "^3.0.0", + "@ohif/extension-default": "^3.0.0", + "@ohif/extension-cornerstone": "^3.0.0", + "@ohif/extension-cornerstone": "^3.0.0", + "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", + "@ohif/extension-dicom-pdf": "^3.0.1", + "@ohif/extension-dicom-video": "^3.0.1" + }, + "dependencies": { + "@babel/runtime": "7.13.10" + }, + "devDependencies": { + "webpack": "^5.50.0", + "webpack-merge": "^5.7.3" + } +} diff --git a/modes/basic-dev-mode/src/id.js b/modes/basic-dev-mode/src/id.js new file mode 100644 index 00000000000..ebe5acd98ae --- /dev/null +++ b/modes/basic-dev-mode/src/id.js @@ -0,0 +1,5 @@ +import packageJson from '../package.json'; + +const id = packageJson.name; + +export { id }; diff --git a/modes/basic-dev-mode/src/index.js b/modes/basic-dev-mode/src/index.js new file mode 100644 index 00000000000..3d586f70a70 --- /dev/null +++ b/modes/basic-dev-mode/src/index.js @@ -0,0 +1,206 @@ +import toolbarButtons from './toolbarButtons.js'; +import { hotkeys } from '@ohif/core'; +import { id } from './id'; + +const configs = { + Length: {}, + // +}; + +const ohif = { + layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', + sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack', + hangingProtocols: '@ohif/extension-default.hangingProtocolModule.default', + measurements: '@ohif/extension-default.panelModule.measure', + thumbnailList: '@ohif/extension-default.panelModule.seriesList', +}; + +const cs3d = { + viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone', +}; + +const dicomsr = { + sopClassHandler: + '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', +}; + +const dicomvideo = { + sopClassHandler: + '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video', + viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video', +}; + +const dicompdf = { + sopClassHandler: '@ohif/extension-dicom-pdf.sopClassHandlerModule.dicom-pdf', + viewport: '@ohif/extension-dicom-pdf.viewportModule.dicom-pdf', +}; + +const extensionDependencies = { + '@ohif/extension-default': '^3.0.0', + '@ohif/extension-cornerstone': '^3.0.0', + '@ohif/extension-cornerstone-dicom-sr': '^3.0.0', + '@ohif/extension-dicom-pdf': '^3.0.1', + '@ohif/extension-dicom-video': '^3.0.1', +}; + +function modeFactory({ modeConfiguration }) { + return { + id, + routeName: 'viewer', + displayName: 'Basic Viewer CS3D', + /** + * Lifecycle hooks + */ + onModeEnter: ({ servicesManager, extensionManager }) => { + const { ToolBarService, ToolGroupService } = servicesManager.services; + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames, Enums } = utilityModule.exports; + + const tools = { + active: [ + { + toolName: toolNames.WindowLevel, + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }, + { + toolName: toolNames.Pan, + bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], + }, + { + toolName: toolNames.Zoom, + bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], + }, + { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + ], + passive: [ + { toolName: toolNames.Length }, + { toolName: toolNames.Bidirectional }, + { toolName: toolNames.Probe }, + { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.RectangleROI }, + { toolName: toolNames.StackScroll }, + ], + // enabled + // disabled + }; + + const toolGroupId = 'default'; + ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, configs); + + let unsubscribe; + + const activateTool = () => { + ToolBarService.recordInteraction({ + groupId: 'WindowLevel', + itemId: 'WindowLevel', + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + }); + + // We don't need to reset the active tool whenever a viewport is getting + // added to the toolGroup. + unsubscribe(); + }; + + // Since we only have one viewport for the basic cs3d mode and it has + // only one hanging protocol, we can just use the first viewport + ({ unsubscribe } = ToolGroupService.subscribe( + ToolGroupService.EVENTS.VIEWPORT_ADDED, + activateTool + )); + + ToolBarService.init(extensionManager); + ToolBarService.addButtons(toolbarButtons); + ToolBarService.createButtonSection('primary', [ + 'MeasurementTools', + 'Zoom', + 'WindowLevel', + 'Pan', + 'Layout', + 'MoreTools', + ]); + }, + onModeExit: ({ servicesManager }) => { + const { + ToolGroupService, + MeasurementService, + ToolBarService, + } = servicesManager.services; + + ToolBarService.reset(); + MeasurementService.clearMeasurements(); + ToolGroupService.destroy(); + }, + validationTags: { + study: [], + series: [], + }, + isValidMode: ({ modalities }) => { + const modalities_list = modalities.split('\\'); + + // Slide Microscopy modality not supported by basic mode yet + return !modalities_list.includes('SM'); + }, + routes: [ + { + path: 'viewer-cs3d', + /*init: ({ servicesManager, extensionManager }) => { + //defaultViewerRouteInit + },*/ + layoutTemplate: ({ location, servicesManager }) => { + return { + id: ohif.layout, + props: { + // TODO: Should be optional, or required to pass empty array for slots? + leftPanels: [ohif.thumbnailList], + rightPanels: [ohif.measurements], + viewports: [ + { + namespace: cs3d.viewport, + displaySetsToDisplay: [ohif.sopClassHandler], + }, + { + namespace: dicomvideo.viewport, + displaySetsToDisplay: [dicomvideo.sopClassHandler], + }, + { + namespace: dicompdf.viewport, + displaySetsToDisplay: [dicompdf.sopClassHandler], + }, + ], + }, + }; + }, + }, + ], + extensions: extensionDependencies, + hangingProtocols: [ohif.hangingProtocols], + sopClassHandlers: [ + dicomvideo.sopClassHandler, + ohif.sopClassHandler, + dicompdf.sopClassHandler, + dicomsr.sopClassHandler, + ], + hotkeys: [...hotkeys.defaults.hotkeyBindings], + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +}; + +export default mode; diff --git a/modes/basic-dev-mode/src/toolbarButtons.js b/modes/basic-dev-mode/src/toolbarButtons.js new file mode 100644 index 00000000000..5731fa07570 --- /dev/null +++ b/modes/basic-dev-mode/src/toolbarButtons.js @@ -0,0 +1,331 @@ +// TODO: torn, can either bake this here; or have to create a whole new button type +// Only ways that you can pass in a custom React component for render :l +import { + // ExpandableToolbarButton, + // ListMenu, + WindowLevelMenuItem, +} from '@ohif/ui'; +import { defaults } from '@ohif/core'; + +const { windowLevelPresets } = defaults; +/** + * + * @param {*} type - 'tool' | 'action' | 'toggle' + * @param {*} id + * @param {*} icon + * @param {*} label + */ +function _createButton(type, id, icon, label, commands, tooltip) { + return { + id, + icon, + label, + type, + commands, + tooltip, + }; +} + +const _createActionButton = _createButton.bind(null, 'action'); +const _createToggleButton = _createButton.bind(null, 'toggle'); +const _createToolButton = _createButton.bind(null, 'tool'); + +/** + * + * @param {*} preset - preset number (from above import) + * @param {*} title + * @param {*} subtitle + */ +function _createWwwcPreset(preset, title, subtitle) { + return { + id: preset.toString(), + title, + subtitle, + type: 'action', + commands: [ + { + commandName: 'setWindowLevel', + commandOptions: { + ...windowLevelPresets[preset], + }, + context: 'CORNERSTONE', + }, + ], + }; +} + +const toolbarButtons = [ + // Measurement + { + id: 'MeasurementTools', + type: 'ohif.splitButton', + props: { + groupId: 'MeasurementTools', + isRadio: true, // ? + // Switch? + primary: _createToolButton( + 'Length', + 'tool-length', + 'Length', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Length', + }, + context: 'CORNERSTONE', + }, + ], + 'Length' + ), + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Measure Tools', + }, + items: [ + _createToolButton( + 'Length', + 'tool-length', + 'Length', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Length', + }, + context: 'CORNERSTONE', + }, + ], + 'Length Tool' + ), + _createToolButton( + 'Bidirectional', + 'tool-bidirectional', + 'Bidirectional', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Bidirectional', + }, + context: 'CORNERSTONE', + }, + ], + 'Bidirectional Tool' + ), + _createToolButton( + 'EllipticalROI', + 'tool-elipse', + 'Ellipse', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'EllipticalROI', + }, + context: 'CORNERSTONE', + }, + ], + 'Ellipse Tool' + ), + ], + }, + }, + // Zoom.. + { + id: 'Zoom', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-zoom', + label: 'Zoom', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Zoom', + }, + context: 'CORNERSTONE', + }, + ], + }, + }, + // Window Level + Presets... + { + id: 'WindowLevel', + type: 'ohif.splitButton', + props: { + groupId: 'WindowLevel', + primary: _createToolButton( + 'WindowLevel', + 'tool-window-level', + 'Window Level', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + 'Window Level' + ), + secondary: { + icon: 'chevron-down', + label: 'W/L Manual', + isActive: true, + tooltip: 'W/L Presets', + }, + isAction: true, // ? + renderer: WindowLevelMenuItem, + items: [ + _createWwwcPreset(1, 'Soft tissue', '400 / 40'), + _createWwwcPreset(2, 'Lung', '1500 / -600'), + _createWwwcPreset(3, 'Liver', '150 / 90'), + _createWwwcPreset(4, 'Bone', '80 / 40'), + _createWwwcPreset(5, 'Brain', '2500 / 480'), + ], + }, + }, + // Pan... + { + id: 'Pan', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-move', + label: 'Pan', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Pan', + }, + context: 'CORNERSTONE', + }, + ], + }, + }, + { + id: 'Capture', + type: 'ohif.action', + props: { + icon: 'tool-capture', + label: 'Capture', + type: 'action', + commands: [ + { + commandName: 'showDownloadViewportModal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + }, + }, + { + id: 'Layout', + type: 'ohif.layoutSelector', + }, + // More... + { + id: 'MoreTools', + type: 'ohif.splitButton', + props: { + isRadio: true, // ? + groupId: 'MoreTools', + primary: _createActionButton( + 'Reset', + 'tool-reset', + 'Reset View', + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Reset' + ), + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Tools', + }, + items: [ + _createActionButton( + 'Reset', + 'tool-reset', + 'Reset View', + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Reset' + ), + _createActionButton( + 'rotate-right', + 'tool-rotate-right', + 'Rotate Right', + [ + { + commandName: 'rotateViewportCW', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Rotate +90' + ), + _createActionButton( + 'flip-horizontal', + 'tool-flip-horizontal', + 'Flip Horizontally', + [ + { + commandName: 'flipViewportHorizontal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Flip Horizontal' + ), + _createToolButton( + 'StackScroll', + 'tool-stack-scroll', + 'Stack Scroll', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'StackScroll', + }, + context: 'CORNERSTONE', + }, + ], + 'Stack Scroll' + ), + _createActionButton( + 'invert', + 'tool-invert', + 'Invert', + [ + { + commandName: 'invertViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], + 'Invert Colors' + ), + ], + }, + }, +]; + +export default toolbarButtons; diff --git a/modes/longitudinal/.webpack/webpack.prod.js b/modes/longitudinal/.webpack/webpack.prod.js index e932fd3afd9..ecd8874190d 100644 --- a/modes/longitudinal/.webpack/webpack.prod.js +++ b/modes/longitudinal/.webpack/webpack.prod.js @@ -15,7 +15,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/modes/longitudinal/README.md b/modes/longitudinal/README.md new file mode 100644 index 00000000000..4b6b907f463 --- /dev/null +++ b/modes/longitudinal/README.md @@ -0,0 +1,60 @@ +# Measurement Tracking Mode + + + +## Introduction +Measurement tracking mode allows you to: + +- Draw annotations and have them shown in the measurement panel +- Create a report from the tracked measurement and export them as DICOM SR +- Use already exported DICOM SR to re-hydrate the measurements in the viewer + +![preview](https://user-images.githubusercontent.com/7490180/171255703-e6d46da8-8d12-4685-b358-0c8d4d5cb5fe.png) + +## Workflow + + +### Status Icon +Each viewport has a left icon indicating whether the series within the viewport contains: + +- tracked measurement OR +- untracked measurement OR +- Structured Report OR +- Locked (uneditable) Structured Report + +In the following, we will discuss each category. + +![tracked](https://user-images.githubusercontent.com/7490180/171255750-c6903338-c295-4553-b8aa-8cb6a8d63943.png) + +### Tracked vs Untracked Measurements + +OHIF-v3 implements a workflow for measurement tracking that can be seen below. +In summary, when you create an annotation, a prompt will be shown whether to start tracking or not. If you start the tracking, the annotation style will change to a solid line, and annotation details get displayed on the measurement panel. On the other hand, if you decline the tracking prompt, the measurement will be considered "temporary," and annotation style remains as a dashed line and not shown on the right panel, and cannot be exported. + +Below, you can see different icons that appear for a tracked vs. untracked series in OHIF-v3. + + +![workflow](https://user-images.githubusercontent.com/7490180/171255780-dd249cbf-dd61-4e02-8d46-b91e01d53529.png) + + +### Reading and Writing DICOM SR +OHIF-v3 provides full support for reading, writing and mapping the DICOM Structured Report (SR) to interactable Cornerstone Tools. When you load an already exported DICOM SR into the viewer, you will be prompted whether to track the measurements for the series or not. + + +![preview](https://user-images.githubusercontent.com/7490180/171255797-6c374780-8e94-4a7f-a125-69b67c18c18c.png) + +If you click Yes, DICOM SR measurements gets re-hydrated into the viewer and the series become a tracked series. However, If you say no and later decide to say track the measurements, you can always click on the SR button that will prompt you with the same message again. + + +![restore](https://user-images.githubusercontent.com/7490180/171255813-8d460bd7-e64d-4bce-9467-ad5cf2615c56.png) + +The full workflow for saving measurements to SR and loading SR into the viewer is shown below. + +![sr-import](https://user-images.githubusercontent.com/7490180/171255826-c308ead6-9dad-4e91-9411-df62658cc839.png) + + +### Loading DICOM SR into an Already Tracked Series + +If you have an already tracked series and try to load a DICOM SR measurements, you will be shown the following lock icon. This means that, you can review the DICOM SR measurement, manipulate image and draw "temporary" measurements; however, you cannot edit the DICOM SR measurement. + +![locked](https://user-images.githubusercontent.com/7490180/171255842-91b84f91-4e1c-4a20-b4a2-cf9653560c43.png) diff --git a/modes/longitudinal/assets/locked.png b/modes/longitudinal/assets/locked.png new file mode 100644 index 00000000000..40e782045b0 Binary files /dev/null and b/modes/longitudinal/assets/locked.png differ diff --git a/modes/longitudinal/assets/preview.png b/modes/longitudinal/assets/preview.png new file mode 100644 index 00000000000..2b8cfb39321 Binary files /dev/null and b/modes/longitudinal/assets/preview.png differ diff --git a/modes/longitudinal/assets/restore.png b/modes/longitudinal/assets/restore.png new file mode 100644 index 00000000000..cfd6622d2ba Binary files /dev/null and b/modes/longitudinal/assets/restore.png differ diff --git a/modes/longitudinal/assets/sr-import.png b/modes/longitudinal/assets/sr-import.png new file mode 100644 index 00000000000..0d31e4c8dab Binary files /dev/null and b/modes/longitudinal/assets/sr-import.png differ diff --git a/modes/longitudinal/assets/tracked.png b/modes/longitudinal/assets/tracked.png new file mode 100644 index 00000000000..7da69fbaa48 Binary files /dev/null and b/modes/longitudinal/assets/tracked.png differ diff --git a/modes/longitudinal/assets/workflow.png b/modes/longitudinal/assets/workflow.png new file mode 100644 index 00000000000..2291ac3ad87 Binary files /dev/null and b/modes/longitudinal/assets/workflow.png differ diff --git a/modes/longitudinal/package.json b/modes/longitudinal/package.json index be63edf5527..82dc751ab46 100644 --- a/modes/longitudinal/package.json +++ b/modes/longitudinal/package.json @@ -35,7 +35,7 @@ "@ohif/core": "^3.0.0", "@ohif/extension-default": "^3.0.0", "@ohif/extension-cornerstone": "^3.0.0", - "@ohif/extension-dicom-sr": "^3.0.0", + "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", "@ohif/extension-dicom-pdf": "^3.0.1", "@ohif/extension-dicom-video": "^3.0.1", "@ohif/extension-measurement-tracking": "^3.0.0" diff --git a/modes/longitudinal/src/index.js b/modes/longitudinal/src/index.js index 3158bd4f4d3..e902ba1fff2 100644 --- a/modes/longitudinal/src/index.js +++ b/modes/longitudinal/src/index.js @@ -1,6 +1,7 @@ import { hotkeys } from '@ohif/core'; import toolbarButtons from './toolbarButtons.js'; import { id } from './id.js'; +import initToolGroups from './initToolGroups.js'; const ohif = { layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', @@ -17,8 +18,9 @@ const tracked = { }; const dicomsr = { - sopClassHandler: '@ohif/extension-dicom-sr.sopClassHandlerModule.dicom-sr', - viewport: '@ohif/extension-dicom-sr.viewportModule.dicom-sr', + sopClassHandler: + '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', }; const dicomvideo = { @@ -37,7 +39,7 @@ const extensionDependencies = { '@ohif/extension-default': '^3.0.0', '@ohif/extension-cornerstone': '^3.0.0', '@ohif/extension-measurement-tracking': '^3.0.0', - '@ohif/extension-dicom-sr': '^3.0.0', + '@ohif/extension-cornerstone-dicom-sr': '^3.0.0', '@ohif/extension-dicom-pdf': '^3.0.1', '@ohif/extension-dicom-video': '^3.0.1', }; @@ -52,20 +54,41 @@ function modeFactory({ modeConfiguration }) { /** * Lifecycle hooks */ - onModeEnter: ({ servicesManager, extensionManager }) => { - // Note: If tool's aren't initialized, this doesn't have viewport/tools - // to "set active". This is mostly for the toolbar UI state? - // Could update tool manager to be always persistent, and to set state - // on load? - const { ToolBarService } = servicesManager.services; - const interaction = { - groupId: 'primary', - itemId: 'Wwwc', - interactionType: 'tool', - commandOptions: undefined, + onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { + const { ToolBarService, ToolGroupService } = servicesManager.services; + + // Init Default and SR ToolGroups + initToolGroups(extensionManager, ToolGroupService, commandsManager); + + let unsubscribe; + + const activateTool = () => { + ToolBarService.recordInteraction({ + groupId: 'WindowLevel', + itemId: 'WindowLevel', + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + }); + + // We don't need to reset the active tool whenever a viewport is getting + // added to the toolGroup. + unsubscribe(); }; - ToolBarService.recordInteraction(interaction); + // Since we only have one viewport for the basic cs3d mode and it has + // only one hanging protocol, we can just use the first viewport + ({ unsubscribe } = ToolGroupService.subscribe( + ToolGroupService.EVENTS.VIEWPORT_ADDED, + activateTool + )); ToolBarService.init(extensionManager); ToolBarService.addButtons(toolbarButtons); @@ -79,7 +102,19 @@ function modeFactory({ modeConfiguration }) { 'MoreTools', ]); }, - onModeExit: () => {}, + onModeExit: ({ servicesManager }) => { + const { + ToolGroupService, + SyncGroupService, + MeasurementService, + ToolBarService, + } = servicesManager.services; + + ToolBarService.reset(); + MeasurementService.clearMeasurements(); + ToolGroupService.destroy(); + SyncGroupService.destroy(); + }, validationTags: { study: [], series: [], diff --git a/modes/longitudinal/src/initToolGroups.js b/modes/longitudinal/src/initToolGroups.js new file mode 100644 index 00000000000..bd31ea75de3 --- /dev/null +++ b/modes/longitudinal/src/initToolGroups.js @@ -0,0 +1,147 @@ +function initDefaultToolGroup( + extensionManager, + ToolGroupService, + commandsManager +) { + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames, Enums } = utilityModule.exports; + + const tools = { + active: [ + { + toolName: toolNames.WindowLevel, + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }, + { + toolName: toolNames.Pan, + bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], + }, + { + toolName: toolNames.Zoom, + bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], + }, + { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + ], + passive: [ + { toolName: toolNames.Length }, + { toolName: toolNames.ArrowAnnotate }, + { toolName: toolNames.Bidirectional }, + { toolName: toolNames.DragProbe }, + { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.RectangleROI }, + { toolName: toolNames.StackScroll }, + { toolName: toolNames.Angle }, + { toolName: toolNames.Magnify }, + ], + // enabled + // disabled + }; + + const toolsConfig = { + [toolNames.ArrowAnnotate]: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }; + + const toolGroupId = 'default'; + ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); +} + +function initSRToolGroup(extensionManager, ToolGroupService, commandsManager) { + const SRUtilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone-dicom-sr.utilityModule.tools' + ); + + const CS3DUtilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames: SRToolNames } = SRUtilityModule.exports; + const { toolNames, Enums } = CS3DUtilityModule.exports; + const tools = { + active: [ + { + toolName: toolNames.WindowLevel, + bindings: [ + { + mouseButton: Enums.MouseBindings.Primary, + }, + ], + }, + { + toolName: toolNames.Pan, + bindings: [ + { + mouseButton: Enums.MouseBindings.Auxiliary, + }, + ], + }, + { + toolName: toolNames.Zoom, + bindings: [ + { + mouseButton: Enums.MouseBindings.Secondary, + }, + ], + }, + { + toolName: toolNames.StackScrollMouseWheel, + bindings: [], + }, + ], + passive: [ + { toolName: SRToolNames.SRLength }, + { toolName: SRToolNames.SRArrowAnnotate }, + { toolName: SRToolNames.SRBidirectional }, + { toolName: SRToolNames.SREllipticalROI }, + ], + enabled: [ + { + toolName: SRToolNames.DICOMSRDisplay, + bindings: [], + }, + ], + // disabled + }; + + const toolsConfig = { + [toolNames.ArrowAnnotate]: { + getTextCallback: (callback, eventDetails) => + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }), + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }; + + const toolGroupId = 'SRToolGroup'; + ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, toolsConfig); +} + +function initToolGroups(extensionManager, ToolGroupService, commandsManager) { + initDefaultToolGroup(extensionManager, ToolGroupService, commandsManager); + initSRToolGroup(extensionManager, ToolGroupService, commandsManager); +} + +export default initToolGroups; diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js index 3a135d7ecd3..ee1fa502f95 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.js @@ -15,14 +15,13 @@ const { windowLevelPresets } = defaults; * @param {*} icon * @param {*} label */ -function _createButton(type, id, icon, label, commandName, commandOptions, tooltip) { +function _createButton(type, id, icon, label, commands, tooltip) { return { id, icon, label, type, - commandName, - commandOptions, + commands, tooltip, }; } @@ -43,12 +42,19 @@ function _createWwwcPreset(preset, title, subtitle) { title, subtitle, type: 'action', - commandName: 'setWindowLevel', - commandOptions: windowLevelPresets[preset], + commands: [ + { + commandName: 'setWindowLevel', + commandOptions: { + ...windowLevelPresets[preset], + }, + context: 'CORNERSTONE', + }, + ], }; } -export default [ +const toolbarButtons = [ // Measurement { id: 'MeasurementTools', @@ -61,8 +67,24 @@ export default [ 'Length', 'tool-length', 'Length', - undefined, - { toolName: 'Length' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Length', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRLength', + toolGroupId: 'SRToolGroup', + }, + // we can use the setToolActive command for this from Cornerstone commandsModule + context: 'CORNERSTONE', + }, + ], 'Length' ), secondary: { @@ -76,34 +98,93 @@ export default [ 'Length', 'tool-length', 'Length', - undefined, - { toolName: 'Length' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Length', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRLength', + toolGroupId: 'SRToolGroup', + }, + // we can use the setToolActive command for this from Cornerstone commandsModule + context: 'CORNERSTONE', + }, + ], 'Length Tool' ), _createToolButton( 'Bidirectional', 'tool-bidirectional', 'Bidirectional', - undefined, - { toolName: 'Bidirectional' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Bidirectional', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRBidirectional', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], 'Bidirectional Tool' ), _createToolButton( 'ArrowAnnotate', 'tool-annotate', 'Annotation', - undefined, - { toolName: 'ArrowAnnotate' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'ArrowAnnotate', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SRArrowAnnotate', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], 'Arrow Annotate' ), _createToolButton( - 'EllipticalRoi', + 'EllipticalROI', 'tool-elipse', 'Ellipse', - undefined, - { - toolName: 'EllipticalRoi', - }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'EllipticalROI', + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'SREllipticalROI', + toolGroupId: 'SRToolGroup', + }, + context: 'CORNERSTONE', + }, + ], 'Ellipse Tool' ), ], @@ -117,7 +198,15 @@ export default [ type: 'tool', icon: 'tool-zoom', label: 'Zoom', - commandOptions: { toolName: 'Zoom' }, + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Zoom', + }, + context: 'CORNERSTONE', + }, + ], }, }, // Window Level + Presets... @@ -125,13 +214,20 @@ export default [ id: 'WindowLevel', type: 'ohif.splitButton', props: { - groupId: 'Wwwc', + groupId: 'WindowLevel', primary: _createToolButton( - 'Wwwc', + 'WindowLevel', 'tool-window-level', 'Window Level', - undefined, - { toolName: 'Wwwc' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], 'Window Level' ), secondary: { @@ -159,7 +255,15 @@ export default [ type: 'tool', icon: 'tool-move', label: 'Pan', - commandOptions: { toolName: 'Pan' }, + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Pan', + }, + context: 'CORNERSTONE', + }, + ], }, }, { @@ -169,7 +273,13 @@ export default [ icon: 'tool-capture', label: 'Capture', type: 'action', - commandName: 'showDownloadViewportModal', + commands: [ + { + commandName: 'showDownloadViewportModal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], }, }, { @@ -187,8 +297,13 @@ export default [ 'Reset', 'tool-reset', 'Reset View', - 'resetViewport', - undefined, + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], 'Reset' ), secondary: { @@ -202,83 +317,144 @@ export default [ 'Reset', 'tool-reset', 'Reset View', - 'resetViewport', - undefined, + [ + { + commandName: 'resetViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], 'Reset' ), _createActionButton( 'rotate-right', 'tool-rotate-right', 'Rotate Right', - 'rotateViewportCW', - undefined, + [ + { + commandName: 'rotateViewportCW', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], 'Rotate +90' ), _createActionButton( 'flip-horizontal', 'tool-flip-horizontal', 'Flip Horizontally', - 'flipViewportHorizontal', - undefined, + [ + { + commandName: 'flipViewportHorizontal', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], 'Flip Horizontal' ), _createToolButton( 'StackScroll', 'tool-stack-scroll', 'Stack Scroll', - undefined, - { toolName: 'StackScroll' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'StackScroll', + }, + context: 'CORNERSTONE', + }, + ], 'Stack Scroll' ), - _createToolButton( - 'Magnify', - 'tool-magnify', - 'Magnify', - undefined, - { toolName: 'Magnify' }, - 'Magnify' - ), _createActionButton( 'invert', 'tool-invert', 'Invert', - 'invertViewport', - undefined, + [ + { + commandName: 'invertViewport', + commandOptions: {}, + context: 'CORNERSTONE', + }, + ], 'Invert Colors' ), + _createToolButton( + 'Probe', + 'tool-probe', + 'Probe', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'DragProbe', + }, + context: 'CORNERSTONE', + }, + ], + 'Probe' + ), _createToggleButton( 'cine', 'tool-cine', 'Cine', - 'toggleCine', - undefined, + [ + { + commandName: 'toggleCine', + context: 'CORNERSTONE', + }, + ], 'Cine' ), _createToolButton( 'Angle', 'tool-angle', 'Angle', - undefined, - { toolName: 'Angle' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Angle', + }, + context: 'CORNERSTONE', + }, + ], 'Angle' ), _createToolButton( - 'DragProbe', - 'tool-probe', - 'Probe', - undefined, - { toolName: 'DragProbe' }, - 'Probe' + 'Magnify', + 'tool-magnify', + 'Magnify', + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'Magnify', + }, + context: 'CORNERSTONE', + }, + ], + 'Magnify' ), _createToolButton( 'Rectangle', 'tool-rectangle', 'Rectangle', - undefined, - { toolName: 'RectangleRoi' }, + [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: 'RectangleROI', + }, + context: 'CORNERSTONE', + }, + ], 'Rectangle' ), ], }, }, ]; + +export default toolbarButtons; diff --git a/modes/tmtv/.webpack/webpack.dev.js b/modes/tmtv/.webpack/webpack.dev.js new file mode 100644 index 00000000000..db7c206b134 --- /dev/null +++ b/modes/tmtv/.webpack/webpack.dev.js @@ -0,0 +1,8 @@ +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + return webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); +}; diff --git a/modes/tmtv/.webpack/webpack.prod.js b/modes/tmtv/.webpack/webpack.prod.js new file mode 100644 index 00000000000..ecd8874190d --- /dev/null +++ b/modes/tmtv/.webpack/webpack.prod.js @@ -0,0 +1,50 @@ +const webpack = require('webpack'); +const { merge } = require('webpack-merge'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const pkg = require('./../package.json'); +const webpackCommon = require('./../../../.webpack/webpack.base.js'); + +const ROOT_DIR = path.join(__dirname, './../'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +const fileName = 'index.umd.js'; +module.exports = (env, argv) => { + const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); + + return merge(commonConfig, { + stats: { + colors: true, + hash: true, + timings: true, + assets: true, + chunks: false, + chunkModules: false, + modules: false, + children: false, + warnings: true, + }, + optimization: { + minimize: true, + sideEffects: true, + }, + output: { + path: ROOT_DIR, + library: 'OHIFExtCornerstone', + libraryTarget: 'umd', + libraryExport: 'default', + filename: pkg.main, + }, + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + new MiniCssExtractPlugin({ + filename: './dist/[name].css', + chunkFilename: './dist/[id].css', + }), + ], + }); +}; diff --git a/modes/tmtv/LICENSE b/modes/tmtv/LICENSE new file mode 100644 index 00000000000..19e20dd35ca --- /dev/null +++ b/modes/tmtv/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Open Health Imaging Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modes/tmtv/README.md b/modes/tmtv/README.md new file mode 100644 index 00000000000..5f33c859d7a --- /dev/null +++ b/modes/tmtv/README.md @@ -0,0 +1,80 @@ +# Total Metabolic Tumor Volume + +# NOTE: THIS MODE IS STILL A WORK IN PROGRESS + +## Introduction + +Total Metabolic Tumor Volume (TMTV) workflow mode enables quantitatively measurement of a tumor volume in a patient's body. +This mode is accessible in any study that has a PT and CT image series as you can see below + + +![modeValid](https://user-images.githubusercontent.com/7490180/171256138-7a948654-6836-460c-817a-fa9a1929926b.png) + +Note: If the study does not have a PT and CT image series, the TMTV workflow mode will not be available +and will become grayed out. + +## Layout +The designed layout for the viewports follows a predefined hanging protocol which will place +10 viewports containing CT, PT, Fusion and Maximum Intensity Projection (MIP) PT scenes. + +The hanging protocol will match the CT and PT displaySets based on series description. In terms +of PT displaySets, the hanging protocol will match the PT displaySet that has attenuated +corrected PET image data. + +As seen in the image below, the first row contains CT volume in 3 different views of Axial, +Sagittal and Coronal. The second row contains PT volume in the same views as the first row. +The last row contains the fusion volume and the viewport to the right is a MIP of the PT +Volume in the Sagittal view. + + + +![modeLayout](https://user-images.githubusercontent.com/7490180/171256159-1e94edac-985f-4de3-8759-27a077541f8f.png) + +## Synchronization + +The viewports in the 3 rows are synchronized both for the Camera and WindowLevel. +It means that when you interact with the CT viewport (pan, zoom, scroll), +the PT and Fusion viewports will be synchronized to the same view. In addition +to camera synchronization, the window level of the CT viewport will be synchronized +with the fusion viewport. + + +### MIP +The tools that are activated on each viewport is unique to its data. For instance, +the mouse scroll tool for PT, CT and Fusion viewports are scrolling through the image data +(in different directions); however, the mouse scroll tool for the MIP viewport will +rotate the camera to match the usecase for the MIP. + + +## Panels +There are two panels that are available in the TMTV workflow mode and we will +discuss them in detail below. + +### SUV Panel +This panel shows the PT metadata derived from the matched PT displaySet. The user +can edit/change the metadata if needed, and by reloading the data the new +metadata will be applied to the PT volume. + + +## ROI Threshold Panel +The ROI Threshold panel is a panel that allows the user to use the `RectangleROIStartEnd` +tool from Cornerstone to define and edit a region of interest. Then, the user can +apply a threshold to the pixels in the ROI and save the result as a segmentation volume. + +By applying each threshold to the ROI, the Total Metabolic Tumor Volume (TMTV), and +the SUV Peak values will get calculated for the labelmap segments and shown in the +panel. + + +## Export Report + +Finally, the results can be saved in the CSV format. The RectangleROI annotations +can also be extracted as a dicom RT Structure Set and saved as a DICOM file. + + +## Video Tutorial + +Below you can see a video tutorial on how to use the TMTV workflow mode. + + +https://user-images.githubusercontent.com/7490180/171065443-35369fba-e955-48ac-94da-d262e0fccb6b.mp4 diff --git a/modes/tmtv/assets/modeLayout.png b/modes/tmtv/assets/modeLayout.png new file mode 100644 index 00000000000..028b579b468 Binary files /dev/null and b/modes/tmtv/assets/modeLayout.png differ diff --git a/modes/tmtv/assets/modeValid.png b/modes/tmtv/assets/modeValid.png new file mode 100644 index 00000000000..69ea706267b Binary files /dev/null and b/modes/tmtv/assets/modeValid.png differ diff --git a/modes/tmtv/babel.config.js b/modes/tmtv/babel.config.js new file mode 100644 index 00000000000..325ca2a8ee7 --- /dev/null +++ b/modes/tmtv/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/modes/tmtv/package.json b/modes/tmtv/package.json new file mode 100644 index 00000000000..ebe4ddbee06 --- /dev/null +++ b/modes/tmtv/package.json @@ -0,0 +1,50 @@ +{ + "name": "@ohif/mode-tmtv", + "version": "3.0.0", + "description": "Total Metabolic Tumor Volume Workflow", + "author": "OHIF", + "license": "MIT", + "repository": "OHIF/Viewers", + "main": "dist/index.umd.js", + "module": "src/index.js", + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1.16.0" + }, + "files": [ + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "keywords": [ + "ohif-mode" + ], + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo", + "dev:cornerstone": "yarn run dev", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "^3.0.0", + "@ohif/extension-default": "^3.0.0", + "@ohif/extension-cornerstone": "^3.0.0", + "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", + "@ohif/extension-dicom-pdf": "^3.0.1", + "@ohif/extension-dicom-video": "^3.0.1", + "@ohif/extension-measurement-tracking": "^3.0.0" + }, + "dependencies": { + "@babel/runtime": "7.16.3" + }, + "devDependencies": { + "webpack": "^5.50.0", + "webpack-merge": "^5.7.3" + } +} diff --git a/modes/tmtv/src/id.js b/modes/tmtv/src/id.js new file mode 100644 index 00000000000..ebe5acd98ae --- /dev/null +++ b/modes/tmtv/src/id.js @@ -0,0 +1,5 @@ +import packageJson from '../package.json'; + +const id = packageJson.name; + +export { id }; diff --git a/modes/tmtv/src/index.js b/modes/tmtv/src/index.js new file mode 100644 index 00000000000..52c9bf69eac --- /dev/null +++ b/modes/tmtv/src/index.js @@ -0,0 +1,200 @@ +import { hotkeys } from '@ohif/core'; +import toolbarButtons from './toolbarButtons.js'; +import { id } from './id.js'; +import initToolGroups, { toolGroupIds } from './initToolGroups.js'; +import setCrosshairsConfiguration from './utils/setCrosshairsConfiguration.js'; +import setEllipticalROIConfiguration from './utils/setEllipticalROIConfiguration.js'; + +const ohif = { + layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', + sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack', + hangingProtocols: '@ohif/extension-default.hangingProtocolModule.default', + measurements: '@ohif/extension-default.panelModule.measure', + thumbnailList: '@ohif/extension-default.panelModule.seriesList', +}; + +const cs3d = { + viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone', +}; + +const tmtv = { + hangingProtocols: '@ohif/extension-tmtv.hangingProtocolModule.ptCT', + petSUV: '@ohif/extension-tmtv.panelModule.petSUV', + ROIThresholdPanel: '@ohif/extension-tmtv.panelModule.ROIThresholdSeg', +}; + +const extensionDependencies = { + // Can derive the versions at least process.env.from npm_package_version + '@ohif/extension-default': '^3.0.0', + '@ohif/extension-cornerstone': '^3.0.0', + '@ohif/extension-tmtv': '^3.0.0', +}; + +let unsubscriptions = []; +function modeFactory({ modeConfiguration }) { + return { + // TODO: We're using this as a route segment + // We should not be. + id, + routeName: 'tmtv', + displayName: 'Total Metabolic Tumor Volume', + /** + * Lifecycle hooks + */ + onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => { + const { + ToolBarService, + ToolGroupService, + HangingProtocolService, + DisplaySetService, + } = servicesManager.services; + + const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.tools' + ); + + const { toolNames, Enums } = utilityModule.exports; + + // Init Default and SR ToolGroups + initToolGroups(toolNames, Enums, ToolGroupService, commandsManager); + + const activateWindowLevel = () => { + ToolBarService.recordInteraction({ + groupId: 'WindowLevel', + itemId: 'WindowLevel', + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName: toolNames.WindowLevel, + toolGroupId: toolGroupIds.CT, + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: toolNames.WindowLevel, + toolGroupId: toolGroupIds.PT, + }, + context: 'CORNERSTONE', + }, + { + commandName: 'setToolActive', + commandOptions: { + toolName: toolNames.WindowLevel, + toolGroupId: toolGroupIds.Fusion, + }, + context: 'CORNERSTONE', + }, + ], + }); + }; + + // Since we only have one viewport for the basic cs3d mode and it has + // only one hanging protocol, we can just use the first viewport + const { unsubscribe } = ToolGroupService.subscribe( + ToolGroupService.EVENTS.VIEWPORT_ADDED, + () => { + activateWindowLevel(); + // For fusion toolGroup we need to add the volumeIds for the crosshairs + // since in the fusion viewport we don't want both PT and CT to render MIP + // when slabThickness is modified + const matches = HangingProtocolService.getDisplaySetsMatchDetails(); + + setCrosshairsConfiguration( + matches, + toolNames, + ToolGroupService, + DisplaySetService + ); + + setEllipticalROIConfiguration( + matches, + toolNames, + ToolGroupService, + DisplaySetService + ); + } + ); + + unsubscriptions.push(unsubscribe); + ToolBarService.init(extensionManager); + ToolBarService.addButtons(toolbarButtons); + ToolBarService.createButtonSection('primary', [ + 'MeasurementTools', + 'Zoom', + 'WindowLevel', + 'Crosshairs', + 'Pan', + 'RectangleROIStartEndThreshold', + 'fusionPTColormap', + ]); + }, + onModeExit: ({ servicesManager }) => { + const { + ToolGroupService, + SyncGroupService, + MeasurementService, + ToolBarService, + } = servicesManager.services; + + unsubscriptions.forEach(unsubscribe => unsubscribe()); + ToolBarService.reset(); + MeasurementService.clearMeasurements(); + ToolGroupService.destroy(); + SyncGroupService.destroy(); + }, + validationTags: { + study: [], + series: [], + }, + isValidMode: ({ modalities }) => { + const modalities_list = modalities.split('\\'); + const invalidModalities = ['SM']; + + // there should be both CT and PT modalities and the modality should not be SM + return ( + modalities_list.includes('CT') && + modalities_list.includes('PT') && + !invalidModalities.some(modality => modalities_list.includes(modality)) + ); + }, + routes: [ + { + path: 'tmtv', + /*init: ({ servicesManager, extensionManager }) => { + //defaultViewerRouteInit + },*/ + layoutTemplate: ({ location, servicesManager }) => { + return { + id: ohif.layout, + props: { + leftPanels: [], + rightPanels: [tmtv.petSUV, tmtv.ROIThresholdPanel], + viewports: [ + { + namespace: cs3d.viewport, + displaySetsToDisplay: [ohif.sopClassHandler], + }, + ], + }, + }; + }, + }, + ], + extensions: extensionDependencies, + hangingProtocols: [ohif.hangingProtocols, tmtv.hangingProtocols], + sopClassHandlers: [ohif.sopClassHandler], + hotkeys: [...hotkeys.defaults.hotkeyBindings], + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +}; + +export default mode; diff --git a/modes/tmtv/src/initToolGroups.js b/modes/tmtv/src/initToolGroups.js new file mode 100644 index 00000000000..1ae73946a20 --- /dev/null +++ b/modes/tmtv/src/initToolGroups.js @@ -0,0 +1,124 @@ +export const toolGroupIds = { + CT: 'ctToolGroup', + PT: 'ptToolGroup', + Fusion: 'fusionToolGroup', + MIP: 'mipToolGroup', + default: 'default', +}; + +function initToolGroups(toolNames, Enums, ToolGroupService, commandsManager) { + const tools = { + active: [ + { + toolName: toolNames.WindowLevel, + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }, + { + toolName: toolNames.Pan, + bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }], + }, + { + toolName: toolNames.Zoom, + bindings: [{ mouseButton: Enums.MouseBindings.Secondary }], + }, + { toolName: toolNames.StackScrollMouseWheel, bindings: [] }, + ], + passive: [ + { toolName: toolNames.Length }, + { toolName: toolNames.ArrowAnnotate }, + { toolName: toolNames.Bidirectional }, + { toolName: toolNames.DragProbe }, + { toolName: toolNames.EllipticalROI }, + { toolName: toolNames.RectangleROI }, + { toolName: toolNames.StackScroll }, + { toolName: toolNames.Angle }, + { toolName: toolNames.Magnify }, + ], + enabled: [{ toolName: toolNames.SegmentationDisplay }], + disabled: [{ toolName: toolNames.Crosshairs }], + }; + + const toolsConfig = { + [toolNames.Crosshairs]: { + viewportIndicators: false, + autoPan: { + enabled: true, + panSize: 10, + }, + }, + [toolNames.ArrowAnnotate]: { + getTextCallback: (callback, eventDetails) => { + commandsManager.runCommand('arrowTextCallback', { + callback, + eventDetails, + }); + }, + + changeTextCallback: (data, eventDetails, callback) => + commandsManager.runCommand('arrowTextCallback', { + callback, + data, + eventDetails, + }), + }, + }; + + ToolGroupService.createToolGroupAndAddTools( + toolGroupIds.CT, + tools, + toolsConfig + ); + ToolGroupService.createToolGroupAndAddTools( + toolGroupIds.PT, + { + active: tools.active, + passive: [ + ...tools.passive, + { toolName: 'RectangleROIStartEndThreshold' }, + ], + enabled: tools.enabled, + disabled: tools.disabled, + }, + toolsConfig + ); + ToolGroupService.createToolGroupAndAddTools( + toolGroupIds.Fusion, + tools, + toolsConfig + ); + ToolGroupService.createToolGroupAndAddTools( + toolGroupIds.default, + tools, + toolsConfig + ); + + const mipTools = { + active: [ + { + toolName: toolNames.VolumeRotateMouseWheel, + }, + { + toolName: toolNames.MipJumpToClick, + bindings: [{ mouseButton: Enums.MouseBindings.Primary }], + }, + ], + enabled: [{ toolName: toolNames.SegmentationDisplay }], + }; + + const mipToolsConfig = { + [toolNames.VolumeRotateMouseWheel]: { + rotateIncrementDegrees: 0.1, + }, + [toolNames.MipJumpToClick]: { + targetViewportIds: ['ptAXIAL', 'ptCORONAL', 'ptSAGITTAL'], + }, + }; + + ToolGroupService.createToolGroupAndAddTools( + toolGroupIds.MIP, + mipTools, + mipToolsConfig + ); +} + +export default initToolGroups; diff --git a/modes/tmtv/src/toolbarButtons.js b/modes/tmtv/src/toolbarButtons.js new file mode 100644 index 00000000000..90e54971ca9 --- /dev/null +++ b/modes/tmtv/src/toolbarButtons.js @@ -0,0 +1,324 @@ +// TODO: torn, can either bake this here; or have to create a whole new button type +// Only ways that you can pass in a custom React component for render :l +import { WindowLevelMenuItem } from '@ohif/ui'; +import { defaults } from '@ohif/core'; +import { toolGroupIds } from './initToolGroups'; +const { windowLevelPresets } = defaults; +/** + * + * @param {*} type - 'tool' | 'action' | 'toggle' + * @param {*} id + * @param {*} icon + * @param {*} label + */ +function _createButton(type, id, icon, label, commands, tooltip) { + return { + id, + icon, + label, + type, + commands, + tooltip, + }; +} + +function _createColormap(label, colormap) { + return { + id: label.toString(), + title: label, + subtitle: label, + type: 'action', + commands: [ + { + commandName: 'setFusionPTColormap', + commandOptions: { + toolGroupId: toolGroupIds.Fusion, + colormap, + }, + }, + { + commandName: 'setFusionPTColormap', + commandOptions: { + toolGroupId: toolGroupIds.Fusion, + colormap, + }, + }, + ], + }; +} + +const _createActionButton = _createButton.bind(null, 'action'); +const _createToggleButton = _createButton.bind(null, 'toggle'); +const _createToolButton = _createButton.bind(null, 'tool'); + +/** + * + * @param {*} preset - preset number (from above import) + * @param {*} title + * @param {*} subtitle + */ +function _createWwwcPreset(preset, title, subtitle) { + return { + id: preset.toString(), + title, + subtitle, + type: 'action', + commands: [ + { + commandName: 'setWindowLevel', + commandOptions: { + windowLevel: windowLevelPresets[preset], + }, + context: 'CORNERSTONE', + }, + ], + }; +} + +function _createCommands(commandName, toolName, toolGroupIds) { + return toolGroupIds.map(toolGroupId => ({ + /* It's a command that is being run when the button is clicked. */ + commandName, + commandOptions: { + toolName, + toolGroupId, + }, + context: 'CORNERSTONE', + })); +} + +const toolbarButtons = [ + // Measurement + { + id: 'MeasurementTools', + type: 'ohif.splitButton', + props: { + groupId: 'MeasurementTools', + isRadio: true, // ? + // Switch? + primary: _createToolButton( + 'Length', + 'tool-length', + 'Length', + [ + ..._createCommands('setToolActive', 'Length', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + 'Length' + ), + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Measure Tools', + }, + items: [ + _createToolButton( + 'Length', + 'tool-length', + 'Length', + [ + ..._createCommands('setToolActive', 'Length', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + 'Length Tool' + ), + _createToolButton( + 'Bidirectional', + 'tool-bidirectional', + 'Bidirectional', + [ + ..._createCommands('setToolActive', 'Bidirectional', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + 'Bidirectional Tool' + ), + _createToolButton( + 'ArrowAnnotate', + 'tool-annotate', + 'Annotation', + [ + ..._createCommands('setToolActive', 'ArrowAnnotate', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + 'Arrow Annotate' + ), + _createToolButton( + 'EllipticalROI', + 'tool-elipse', + 'Ellipse', + [ + ..._createCommands('setToolActive', 'EllipticalROI', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + 'Ellipse Tool' + ), + ], + }, + }, + // Zoom.. + { + id: 'Zoom', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-zoom', + label: 'Zoom', + commands: [ + ..._createCommands('setToolActive', 'Zoom', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + }, + }, + // Window Level + Presets... + { + id: 'WindowLevel', + type: 'ohif.splitButton', + props: { + groupId: 'WindowLevel', + primary: _createToolButton( + 'WindowLevel', + 'tool-window-level', + 'Window Level', + [ + ..._createCommands('setToolActive', 'WindowLevel', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + 'Window Level' + ), + secondary: { + icon: 'chevron-down', + label: 'W/L Manual', + isActive: true, + tooltip: 'W/L Presets', + }, + isAction: true, // ? + renderer: WindowLevelMenuItem, + items: [ + _createWwwcPreset(1, 'Soft tissue', '400 / 40'), + _createWwwcPreset(2, 'Lung', '1500 / -600'), + _createWwwcPreset(3, 'Liver', '150 / 90'), + _createWwwcPreset(4, 'Bone', '80 / 40'), + _createWwwcPreset(5, 'Brain', '2500 / 480'), + ], + }, + }, + { + id: 'Crosshairs', + type: 'ohif.radioGroup', + props: { + type: 'toggle', + icon: 'tool-crosshair', + label: 'Crosshairs', + commands: [ + ..._createCommands('toggleCrosshairs', 'Crosshairs', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + }, + }, + // Pan... + { + id: 'Pan', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-move', + label: 'Pan', + commands: [ + ..._createCommands('setToolActive', 'Pan', [ + toolGroupIds.CT, + toolGroupIds.PT, + toolGroupIds.Fusion, + ]), + ], + }, + }, + { + id: 'RectangleROIStartEndThreshold', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-create-threshold', + label: 'Rectangle ROI Threshold', + commands: [ + ..._createCommands('setToolActive', 'RectangleROIStartEndThreshold', [ + toolGroupIds.PT, + ]), + { + commandName: 'displayNotification', + commandOptions: { + title: 'RectangleROI Threshold Tip', + text: + 'RectangleROI Threshold tool should be used on PT Axial Viewport', + type: 'info', + }, + }, + { + commandName: 'setViewportActive', + commandOptions: { + viewportId: 'ptAXIAL', + }, + }, + ], + }, + }, + { + id: 'fusionPTColormap', + type: 'ohif.splitButton', + props: { + groupId: 'fusionPTColormap', + primary: _createToolButton( + 'fusionPTColormap', + 'tool-fusion-color', + 'Fusion PT Colormap', + [], + 'Fusion PT Colormap' + ), + secondary: { + icon: 'chevron-down', + label: 'PT Colormap', + isActive: true, + tooltip: 'PET Image Colormap', + }, + isAction: true, // ? + renderer: WindowLevelMenuItem, + items: [ + _createColormap('Hot Iron', 'hot_iron'), + _createColormap('S PET', 's_pet'), + _createColormap('Ret Hot', 'red_hot'), + _createColormap('Perfusion', 'perfusion'), + _createColormap('Rainbow', 'rainbow_2'), + _createColormap('SUV', 'suv'), + _createColormap('GE 256', 'ge_256'), + _createColormap('GE', 'ge'), + _createColormap('Siemens', 'siemens'), + ], + }, + }, +]; + +export default toolbarButtons; diff --git a/modes/tmtv/src/utils/setCrosshairsConfiguration.js b/modes/tmtv/src/utils/setCrosshairsConfiguration.js new file mode 100644 index 00000000000..3d83fd14a7a --- /dev/null +++ b/modes/tmtv/src/utils/setCrosshairsConfiguration.js @@ -0,0 +1,35 @@ +import { toolGroupIds } from '../initToolGroups'; + +export default function setCrosshairsConfiguration( + matches, + toolNames, + ToolGroupService, + DisplaySetService +) { + const matchDetails = matches.get('ctDisplaySet'); + + if (!matchDetails) { + return; + } + + const { SeriesInstanceUID } = matchDetails; + const displaySets = DisplaySetService.getDisplaySetsForSeries( + SeriesInstanceUID + ); + + const toolConfig = ToolGroupService.getToolConfiguration( + toolGroupIds.Fusion, + toolNames.Crosshairs + ); + + const crosshairsConfig = { + ...toolConfig, + filterActorUIDsToSetSlabThickness: [displaySets[0].displaySetInstanceUID], + }; + + ToolGroupService.setToolConfiguration( + toolGroupIds.Fusion, + toolNames.Crosshairs, + crosshairsConfig + ); +} diff --git a/modes/tmtv/src/utils/setEllipticalROIConfiguration.js b/modes/tmtv/src/utils/setEllipticalROIConfiguration.js new file mode 100644 index 00000000000..826061ac53f --- /dev/null +++ b/modes/tmtv/src/utils/setEllipticalROIConfiguration.js @@ -0,0 +1,40 @@ +import { toolGroupIds } from '../initToolGroups'; + +export default function setEllipticalROIConfiguration( + matches, + toolNames, + ToolGroupService, + DisplaySetService +) { + const matchDetails = matches.get('ptDisplaySet'); + + if (!matchDetails) { + return; + } + + const { SeriesInstanceUID } = matchDetails; + + const displaySets = DisplaySetService.getDisplaySetsForSeries( + SeriesInstanceUID + ); + + if (!displaySets || displaySets.length === 0) { + return; + } + + const toolConfig = ToolGroupService.getToolConfiguration( + toolGroupIds.Fusion, + toolNames.EllipticalROI + ); + + const ellipticalROIConfig = { + ...toolConfig, + volumeId: displaySets[0].displaySetInstanceUID, + }; + + ToolGroupService.setToolConfiguration( + toolGroupIds.Fusion, + toolNames.EllipticalROI, + ellipticalROIConfig + ); +} diff --git a/package.json b/package.json index 40c2557d90e..6aba7aec6ed 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "dev:dcm4chee": "lerna run dev:dcm4chee --stream", "dev:static": "lerna run dev:static --stream", "orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up", + "preinstall": "node preinstall.js", "start": "yarn run dev", "test": "yarn run test:unit", "test:data": "git submodule update --init", @@ -55,9 +56,8 @@ }, "dependencies": { "@babel/runtime": "7.16.3", - "core-js": "^3.2.1", - "cornerstone-core": "2.6.0", - "wslink": "^0.1.8" + "@kitware/vtk.js": "^24.18.7", + "core-js": "^3.2.1" }, "peerDependencies": { "react": "17.0.2", @@ -71,8 +71,13 @@ "@babel/plugin-transform-arrow-functions": "^7.16.7", "@babel/plugin-transform-regenerator": "^7.16.7", "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/plugin-transform-typescript": "^7.13.0", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.13.0", + "@types/jest": "^27.5.0", + "@typescript-eslint/eslint-plugin": "^4.19.0", + "@typescript-eslint/parser": "^4.19.0", "autoprefixer": "10.4.4", "babel-eslint": "9.x", "babel-loader": "^8.2.4", @@ -94,6 +99,9 @@ "eslint-plugin-promise": "^5.2.0", "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.4.0", + "eslint-plugin-tsdoc": "^0.2.11", + "eslint-webpack-plugin": "^2.5.3", + "extract-css-chunks-webpack-plugin": "^4.5.4", "html-webpack-plugin": "^5.3.2", "husky": "^3.0.0", "jest": "^24.8.0", @@ -114,6 +122,7 @@ "start-server-and-test": "^1.10.0", "style-loader": "^1.0.0", "terser-webpack-plugin": "^5.1.4", + "typescript": "^4.2.3", "unused-webpack-plugin": "2.4.0", "webpack": "^5.50.0", "webpack-cli": "^4.7.2", diff --git a/platform/cli/templates/extension/.webpack/webpack.prod.js b/platform/cli/templates/extension/.webpack/webpack.prod.js index 11ba8eee5c3..dda0ecdab17 100644 --- a/platform/cli/templates/extension/.webpack/webpack.prod.js +++ b/platform/cli/templates/extension/.webpack/webpack.prod.js @@ -8,7 +8,7 @@ const outputFolder = path.join(__dirname, '../dist'); const config = { mode: 'production', entry: rootDir + '/' + pkg.module, - devtool: 'inline-source-map', + devtool: 'source-map', output: { path: outputFolder, filename: outputFile, @@ -25,23 +25,35 @@ const config = { commonjs: 'react', amd: 'react', }, + '@ohif/core': { + commonjs2: '@ohif/core', + commonjs: '@ohif/core', + amd: '@ohif/core', + root: '@ohif/core', + }, + '@ohif/ui': { + commonjs2: '@ohif/ui', + commonjs: '@ohif/ui', + amd: '@ohif/ui', + root: '@ohif/ui', + }, }, ], module: { rules: [ { - test: /(\.jsx|\.js)$/, + test: /(\.jsx|\.js|\.tsx|\.ts)$/, loader: 'babel-loader', exclude: /(node_modules|bower_components)/, resolve: { - extensions: ['.js', '.jsx'], + extensions: ['.js', '.jsx', '.ts', '.tsx',], }, }, ], }, resolve: { modules: [path.resolve('./node_modules'), path.resolve('./src')], - extensions: ['.json', '.js', '.jsx'], + extensions: ['.json', '.js', '.jsx', '.tsx', '.ts',], }, }; diff --git a/platform/cli/templates/extension/babel.config.js b/platform/cli/templates/extension/babel.config.js index 4afa952b0c1..92fbbdeaf95 100644 --- a/platform/cli/templates/extension/babel.config.js +++ b/platform/cli/templates/extension/babel.config.js @@ -10,6 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, + "@babel/preset-typescript", ], '@babel/preset-react', ], @@ -25,6 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', + "@babel/preset-typescript", ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -33,6 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', + "@babel/preset-typescript", ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/platform/cli/templates/extension/dependencies.json b/platform/cli/templates/extension/dependencies.json index 65b17c9d977..25e498e1c0c 100644 --- a/platform/cli/templates/extension/dependencies.json +++ b/platform/cli/templates/extension/dependencies.json @@ -24,8 +24,8 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-i18next": "^10.11.0", - "react-router": "^6.2.1", - "react-router-dom": "^6.2.1", + "react-router": "^6.3.0", + "react-router-dom": "^6.3.0", "webpack": "^5.50.0", "webpack-merge": "^5.7.3" }, diff --git a/platform/cli/templates/extension/src/index.js b/platform/cli/templates/extension/src/index.tsx similarity index 100% rename from platform/cli/templates/extension/src/index.js rename to platform/cli/templates/extension/src/index.tsx diff --git a/platform/cli/templates/mode/.webpack/webpack.prod.js b/platform/cli/templates/mode/.webpack/webpack.prod.js index 11ba8eee5c3..dda0ecdab17 100644 --- a/platform/cli/templates/mode/.webpack/webpack.prod.js +++ b/platform/cli/templates/mode/.webpack/webpack.prod.js @@ -8,7 +8,7 @@ const outputFolder = path.join(__dirname, '../dist'); const config = { mode: 'production', entry: rootDir + '/' + pkg.module, - devtool: 'inline-source-map', + devtool: 'source-map', output: { path: outputFolder, filename: outputFile, @@ -25,23 +25,35 @@ const config = { commonjs: 'react', amd: 'react', }, + '@ohif/core': { + commonjs2: '@ohif/core', + commonjs: '@ohif/core', + amd: '@ohif/core', + root: '@ohif/core', + }, + '@ohif/ui': { + commonjs2: '@ohif/ui', + commonjs: '@ohif/ui', + amd: '@ohif/ui', + root: '@ohif/ui', + }, }, ], module: { rules: [ { - test: /(\.jsx|\.js)$/, + test: /(\.jsx|\.js|\.tsx|\.ts)$/, loader: 'babel-loader', exclude: /(node_modules|bower_components)/, resolve: { - extensions: ['.js', '.jsx'], + extensions: ['.js', '.jsx', '.ts', '.tsx',], }, }, ], }, resolve: { modules: [path.resolve('./node_modules'), path.resolve('./src')], - extensions: ['.json', '.js', '.jsx'], + extensions: ['.json', '.js', '.jsx', '.tsx', '.ts',], }, }; diff --git a/platform/cli/templates/mode/babel.config.js b/platform/cli/templates/mode/babel.config.js index 4afa952b0c1..92fbbdeaf95 100644 --- a/platform/cli/templates/mode/babel.config.js +++ b/platform/cli/templates/mode/babel.config.js @@ -10,6 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, + "@babel/preset-typescript", ], '@babel/preset-react', ], @@ -25,6 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', + "@babel/preset-typescript", ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -33,6 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', + "@babel/preset-typescript", ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/platform/cli/templates/mode/dependencies.json b/platform/cli/templates/mode/dependencies.json index a671e67df5b..10a6edec776 100644 --- a/platform/cli/templates/mode/dependencies.json +++ b/platform/cli/templates/mode/dependencies.json @@ -31,11 +31,12 @@ "@babel/plugin-transform-arrow-functions": "^7.2.0", "@babel/plugin-transform-regenerator": "^7.4.5", "@babel/plugin-transform-runtime": "^7.5.0", - "babel-plugin-inline-react-svg": "^2.0.1", "@babel/preset-env": "^7.5.0", "@babel/preset-react": "^7.0.0", + "@babel/preset-typescript": "^7.17.12", "babel-eslint": "^8.0.3", "babel-loader": "^8.0.0-beta.4", + "babel-plugin-inline-react-svg": "^2.0.1", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^10.2.0", "cross-env": "^7.0.3", diff --git a/platform/cli/templates/mode/src/index.js b/platform/cli/templates/mode/src/index.tsx similarity index 100% rename from platform/cli/templates/mode/src/index.js rename to platform/cli/templates/mode/src/index.tsx diff --git a/platform/core/.webpack/webpack.prod.js b/platform/core/.webpack/webpack.prod.js index 6ddd016979e..c6c2fb229e4 100644 --- a/platform/core/.webpack/webpack.prod.js +++ b/platform/core/.webpack/webpack.prod.js @@ -11,7 +11,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/platform/core/package.json b/platform/core/package.json index 70d28744494..a545f40f728 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -30,16 +30,14 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage" }, "peerDependencies": { - "cornerstone-core": "^2.6.0", - "cornerstone-tools": "6.0.2", "cornerstone-math": "0.1.9", - "cornerstone-wado-image-loader": "4.0.4", + "cornerstone-wado-image-loader": "^4.2.0", "dicom-parser": "^1.8.9", "@ohif/ui": "^2.0.0" }, "dependencies": { "@babel/runtime": "7.16.3", - "dcmjs": "0.16.1", + "dcmjs": "^0.24.5", "dicomweb-client": "^0.6.0", "isomorphic-base64": "^1.0.2", "lodash.merge": "^4.6.1", diff --git a/platform/core/src/DICOMSR/dataExchange.js b/platform/core/src/DICOMSR/dataExchange.js deleted file mode 100644 index 9d8d1208335..00000000000 --- a/platform/core/src/DICOMSR/dataExchange.js +++ /dev/null @@ -1,240 +0,0 @@ -import log from '../log'; -import utils from '../utils'; -// import { -// retrieveMeasurementFromSR, -// stowSRFromMeasurements, -// } from './handleStructuredReport'; -import findMostRecentStructuredReport from './utils/findMostRecentStructuredReport'; -import cornerstone from 'cornerstone-core'; -import cornerstoneTools from 'cornerstone-tools'; -import dcmjs from 'dcmjs'; - -const { MeasurementReport } = dcmjs.adapters.Cornerstone; - -/** - * - * @typedef serverType - * @property {string} type - type of the server - * @property {string} wadoRoot - server wado root url - * - */ - -/** - * Function to be registered into MeasurementAPI to retrieve measurements from DICOM Structured Reports - * - * @param {serverType} server - * @returns {Promise} Should resolve with OHIF measurementData object - */ -/*const retrieveMeasurements = server => { - log.info('[DICOMSR] retrieveMeasurements'); - - if (!server || server.type !== 'dicomWeb') { - log.error('[DICOMSR] DicomWeb server is required!'); - return Promise.reject({}); - } - - const serverUrl = server.wadoRoot; - const studies = utils.studyMetadataManager.all(); - - const latestSeries = findMostRecentStructuredReport(studies); - - if (!latestSeries) return Promise.resolve({}); - - return retrieveMeasurementFromSR(latestSeries, studies, serverUrl); -};*/ - -/** - * - * @param {object[]} measurementData An array of measurements from the measurements service - * @param {string[]} additionalFindingTypes toolTypes that should be stored with labels as Findings - * @param {object} options Naturalized DICOM JSON headers to merge into the displaySet. - * as opposed to Finding Sites. - * that you wish to serialize. - */ -const downloadReport = ( - measurementData, - additionalFindingTypes, - options = {} -) => { - const srDataset = generateReport( - measurementData, - additionalFindingTypes, - options - ); - const reportBlob = dcmjs.data.datasetToBlob(srDataset); - - //Create a URL for the binary. - var objectUrl = URL.createObjectURL(reportBlob); - window.location.assign(objectUrl); -}; - -/** - * - * @param {object[]} measurementData An array of measurements from the measurements service - * that you wish to serialize. - * @param {string[]} additionalFindingTypes toolTypes that should be stored with labels as Findings - * @param {object} options Naturalized DICOM JSON headers to merge into the displaySet. - * - */ -const generateReport = ( - measurementData, - additionalFindingTypes, - options = {} -) => { - const filteredToolState = _getFilteredCornerstoneToolState( - measurementData, - additionalFindingTypes - ); - const report = MeasurementReport.generateReport( - filteredToolState, - cornerstone.metaData - ); - - const { dataset } = report; - - // Add in top level series options - Object.assign(dataset, options); - - return dataset; -}; - -/** - * - * @param {object[]} measurementData An array of measurements from the measurements service - * that you wish to serialize. - * @param {object} dataSource The dataSource that you wish to use to persist the data. - * @param {string[]} additionalFindingTypes toolTypes that should be stored with labels as Findings - * @param {object} options Naturalized DICOM JSON headers to merge into the displaySet. - * @return {object} The naturalized report - */ -const storeMeasurements = async ( - measurementData, - dataSource, - additionalFindingTypes, - options = {} -) => { - // TODO -> Eventually use the measurements directly and not the dcmjs adapter, - // But it is good enough for now whilst we only have cornerstone as a datasource. - log.info('[DICOMSR] storeMeasurements'); - - if (!dataSource || !dataSource.store || !dataSource.store.dicom) { - log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!'); - return Promise.reject({}); - } - - try { - const naturalizedReport = generateReport( - measurementData, - additionalFindingTypes, - options - ); - const { StudyInstanceUID } = naturalizedReport; - - await dataSource.store.dicom(naturalizedReport); - - if (StudyInstanceUID) { - dataSource.deleteStudyMetadataPromise(StudyInstanceUID); - } - - return naturalizedReport; - } catch (error) { - console.warn(error); - log.error( - `[DICOMSR] Error while saving the measurements: ${error.message}` - ); - throw new Error(error.message || 'Error while saving the measurements.'); - } -}; - -// _getFilteredCornerstoneToolState -// DIFFERENT IMPLEMENTATION HERE! What's up? -function _getFilteredCornerstoneToolState( - measurementData, - additionalFindingTypes -) { - const uidFilter = measurementData.map(md => md.id); - - const globalToolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState(); - const filteredToolState = {}; - - function addToFilteredToolState(imageId, toolType, toolDataI) { - if (!filteredToolState[imageId]) { - filteredToolState[imageId] = {}; - } - - const imageIdSpecificToolState = filteredToolState[imageId]; - - if (!imageIdSpecificToolState[toolType]) { - imageIdSpecificToolState[toolType] = { - data: [], - }; - } - - const measurmentDataI = measurementData.find(md => md.id === toolDataI.id); - const toolData = imageIdSpecificToolState[toolType].data; - - let finding; - const findingSites = []; - - // NOTE -> Any kind of freetext value abuses the DICOM standard, - // As CodeValues should map 1:1 with CodeMeanings. - // Ideally we would actually use SNOMED codes for this. - if (measurmentDataI.label) { - if (additionalFindingTypes.includes(toolType)) { - finding = { - CodeValue: 'CORNERSTONEFREETEXT', - CodingSchemeDesignator: 'CST4', - CodeMeaning: measurmentDataI.label, - }; - } else { - findingSites.push({ - CodeValue: 'CORNERSTONEFREETEXT', - CodingSchemeDesignator: 'CST4', - CodeMeaning: measurmentDataI.label, - }); - } - } - - const measurement = Object.assign({}, toolDataI, { - finding, - findingSites, - }); - - toolData.push(measurement); - } - - const uids = uidFilter.slice(); - const imageIds = Object.keys(globalToolState); - - for (let i = 0; i < imageIds.length; i++) { - const imageId = imageIds[i]; - const imageIdSpecificToolState = globalToolState[imageId]; - - const toolTypes = Object.keys(imageIdSpecificToolState); - - for (let j = 0; j < toolTypes.length; j++) { - const toolType = toolTypes[j]; - const toolData = imageIdSpecificToolState[toolType].data; - - if (toolData) { - for (let k = 0; k < toolData.length; k++) { - const toolDataK = toolData[k]; - const uidIndex = uids.findIndex(uid => uid === toolDataK.id); - - if (uidIndex !== -1) { - addToFilteredToolState(imageId, toolType, toolDataK); - uids.splice(uidIndex, 1); - - if (!uids.length) { - return filteredToolState; - } - } - } - } - } - } - - return filteredToolState; -} - -export { storeMeasurements, downloadReport }; diff --git a/platform/core/src/DICOMSR/index.js b/platform/core/src/DICOMSR/index.js deleted file mode 100644 index 1168f56f4b4..00000000000 --- a/platform/core/src/DICOMSR/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import { - //retrieveMeasurements, - storeMeasurements, - downloadReport, -} from './dataExchange'; -import isToolSupported from './utils/isToolSupported'; - -const DICOMSR = { - //retrieveMeasurements, - storeMeasurements, - downloadReport, - isToolSupported, -}; - -export default DICOMSR; diff --git a/platform/core/src/classes/CommandsManager.js b/platform/core/src/classes/CommandsManager.js index 88823f95e99..2ff961230ad 100644 --- a/platform/core/src/classes/CommandsManager.js +++ b/platform/core/src/classes/CommandsManager.js @@ -18,16 +18,8 @@ import log from '../log.js'; * to extend this class, please check it's source before adding new methods. */ export class CommandsManager { - constructor({ getActiveContexts } = {}) { + constructor({} = {}) { this.contexts = {}; - - if (!getActiveContexts) { - throw new Error( - 'CommandsManager was instantiated without getActiveContexts()' - ); - } - - this._getActiveContexts = getActiveContexts; } /** @@ -122,12 +114,8 @@ export class CommandsManager { contexts.push(context); } } else { - const activeContexts = this._getActiveContexts(); - activeContexts.forEach(activeContext => { - const context = this.getContext(activeContext); - if (context) { - contexts.push(context); - } + Object.keys(this.contexts).forEach(contextName => { + contexts.push(this.getContext(contextName)); }); } diff --git a/platform/core/src/classes/CommandsManager.test.js b/platform/core/src/classes/CommandsManager.test.js index da260281f0f..69d190e3189 100644 --- a/platform/core/src/classes/CommandsManager.test.js +++ b/platform/core/src/classes/CommandsManager.test.js @@ -12,7 +12,11 @@ describe('CommandsManager', () => { options: { passMeToCommandFn: ':wave:' }, }, commandsManagerConfig = { - getActiveContexts: () => ['VIEWER', 'ACTIVE_VIEWER::CORNERSTONE'], + getAppState: () => { + return { + viewers: 'Test', + }; + }, }; beforeEach(() => { @@ -29,12 +33,6 @@ describe('CommandsManager', () => { expect(localCommandsManager.contexts).toEqual({}); }); - it('throws Error if instantiated without getActiveContexts', () => { - expect(() => { - new CommandsManager(); - }).toThrow(new Error('CommandsManager was instantiated without getActiveContexts()')); - }); - describe('createContext()', () => { it('creates a context', () => { commandsManager.createContext(contextName); diff --git a/platform/core/src/classes/HotkeysManager.test.js b/platform/core/src/classes/HotkeysManager.test.js index 159dab8c70f..1f83c48232c 100644 --- a/platform/core/src/classes/HotkeysManager.test.js +++ b/platform/core/src/classes/HotkeysManager.test.js @@ -21,13 +21,13 @@ describe('HotkeysManager', () => { }); it('has expected properties', () => { const allProperties = Object.keys(hotkeysManager); - const expectedProprties = [ + const expectedProperties = [ 'hotkeyDefinitions', 'hotkeyDefaults', 'isEnabled', ]; - const containsAllExpectedProperties = expectedProprties.every(expected => + const containsAllExpectedProperties = expectedProperties.every(expected => allProperties.includes(expected) ); diff --git a/platform/core/src/classes/MetadataProvider.js b/platform/core/src/classes/MetadataProvider.js index a4b66f60ca4..ed6f8d30735 100644 --- a/platform/core/src/classes/MetadataProvider.js +++ b/platform/core/src/classes/MetadataProvider.js @@ -1,8 +1,10 @@ import queryString from 'query-string'; import dicomParser from 'dicom-parser'; +import { imageIdToURI } from '../utils'; import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacingInformation'; import DicomMetadataStore from '../services/DicomMetadataStore'; import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData'; +import toNumber from '../utils/toNumber'; class MetadataProvider { constructor() { @@ -13,7 +15,17 @@ class MetadataProvider { writable: false, value: new Map(), }); - Object.defineProperty(this, 'imageIdToUIDs', { + Object.defineProperty(this, 'imageURIToUIDs', { + configurable: false, + enumerable: false, + writable: false, + value: new Map(), + }); + // Can be used to store custom metadata for a specific type. + // For instance, the scaling metadata for PET can be stored here + // as type "scalingModule" + // + Object.defineProperty(this, 'customMetadata', { configurable: false, enumerable: false, writable: false, @@ -21,13 +33,21 @@ class MetadataProvider { }); } - addImageIdToUIDs(imageId, uids) { // This method is a fallback for when you don't have WADO-URI or WADO-RS. // You can add instances fetched by any method by calling addInstance, and hook an imageId to point at it here. // An example would be dicom hosted at some random site. + const imageURI = imageIdToURI(imageId); + this.imageURIToUIDs.set(imageURI, uids); + } - this.imageIdToUIDs.set(imageId, uids); + addCustomMetadata(imageId, type, metadata) { + const imageURI = imageIdToURI(imageId); + if (!this.customMetadata.has(type)) { + this.customMetadata.set(type, {}); + } + + this.customMetadata.get(type)[imageURI] = metadata; } _getInstance(imageId) { @@ -53,6 +73,15 @@ class MetadataProvider { return instance; } + // check inside custom metadata + if (this.customMetadata.has(query)) { + const customMetadata = this.customMetadata.get(query); + const imageURI = imageIdToURI(imageId); + if (customMetadata[imageURI]) { + return customMetadata[imageURI]; + } + } + return this.getTagFromInstance(query, instance, options); } @@ -106,7 +135,7 @@ class MetadataProvider { metadata = { modality: instance.Modality, seriesInstanceUID: instance.SeriesInstanceUID, - seriesNumber: validNumber(instance.SeriesNumber), + seriesNumber: toNumber(instance.SeriesNumber), studyInstanceUID: instance.StudyInstanceUID, seriesDate, seriesTime, @@ -114,9 +143,14 @@ class MetadataProvider { break; case WADO_IMAGE_LOADER_TAGS.PATIENT_STUDY_MODULE: metadata = { - patientAge: validNumber(instance.PatientAge), - patientSize: validNumber(instance.PatientSize), - patientWeight: validNumber(instance.PatientWeight), + patientAge: toNumber(instance.PatientAge), + patientSize: toNumber(instance.PatientSize), + patientWeight: toNumber(instance.PatientWeight), + }; + break; + case WADO_IMAGE_LOADER_TAGS.PATIENT_DEMOGRAPHIC_MODULE: + metadata = { + patientSex: instance.PatientSex, }; break; case WADO_IMAGE_LOADER_TAGS.IMAGE_PLANE_MODULE: @@ -145,50 +179,56 @@ class MetadataProvider { metadata = { frameOfReferenceUID: instance.FrameOfReferenceUID, - rows: validNumber(instance.Rows), - columns: validNumber(instance.Columns), - imageOrientationPatient: validNumber(ImageOrientationPatient), - rowCosines: validNumber(rowCosines), - columnCosines: validNumber(columnCosines), - imagePositionPatient: validNumber(instance.ImagePositionPatient), - sliceThickness: validNumber(instance.SliceThickness), - sliceLocation: validNumber(instance.SliceLocation), - pixelSpacing: validNumber(PixelSpacing), - rowPixelSpacing: validNumber(rowPixelSpacing), - columnPixelSpacing: validNumber(columnPixelSpacing), + rows: toNumber(instance.Rows), + columns: toNumber(instance.Columns), + imageOrientationPatient: toNumber(ImageOrientationPatient), + rowCosines: toNumber(rowCosines), + columnCosines: toNumber(columnCosines), + imagePositionPatient: toNumber(instance.ImagePositionPatient), + sliceThickness: toNumber(instance.SliceThickness), + sliceLocation: toNumber(instance.SliceLocation), + pixelSpacing: toNumber(PixelSpacing), + rowPixelSpacing: toNumber(rowPixelSpacing), + columnPixelSpacing: toNumber(columnPixelSpacing), }; break; case WADO_IMAGE_LOADER_TAGS.IMAGE_PIXEL_MODULE: metadata = { - samplesPerPixel: validNumber(instance.SamplesPerPixel), + samplesPerPixel: toNumber(instance.SamplesPerPixel), photometricInterpretation: instance.PhotometricInterpretation, - rows: validNumber(instance.Rows), - columns: validNumber(instance.Columns), - bitsAllocated: validNumber(instance.BitsAllocated), - bitsStored: validNumber(instance.BitsStored), - highBit: validNumber(instance.HighBit), - pixelRepresentation: validNumber(instance.PixelRepresentation), - planarConfiguration: validNumber(instance.PlanarConfiguration), - pixelAspectRatio: validNumber(instance.PixelAspectRatio), - smallestPixelValue: validNumber(instance.SmallestPixelValue), - largestPixelValue: validNumber(instance.LargestPixelValue), - redPaletteColorLookupTableDescriptor: validNumber( + rows: toNumber(instance.Rows), + columns: toNumber(instance.Columns), + bitsAllocated: toNumber(instance.BitsAllocated), + bitsStored: toNumber(instance.BitsStored), + highBit: toNumber(instance.HighBit), + pixelRepresentation: toNumber(instance.PixelRepresentation), + planarConfiguration: toNumber(instance.PlanarConfiguration), + pixelAspectRatio: toNumber(instance.PixelAspectRatio), + smallestPixelValue: toNumber(instance.SmallestPixelValue), + largestPixelValue: toNumber(instance.LargestPixelValue), + redPaletteColorLookupTableDescriptor: toNumber( instance.RedPaletteColorLookupTableDescriptor ), - greenPaletteColorLookupTableDescriptor: validNumber( + greenPaletteColorLookupTableDescriptor: toNumber( instance.GreenPaletteColorLookupTableDescriptor ), - bluePaletteColorLookupTableDescriptor: validNumber( + bluePaletteColorLookupTableDescriptor: toNumber( instance.BluePaletteColorLookupTableDescriptor ), redPaletteColorLookupTableData: fetchPaletteColorLookupTableData( - instance, "RedPaletteColorLookupTableData", "RedPaletteColorLookupTableDescriptor" + instance, + 'RedPaletteColorLookupTableData', + 'RedPaletteColorLookupTableDescriptor' ), greenPaletteColorLookupTableData: fetchPaletteColorLookupTableData( - instance, "GreenPaletteColorLookupTableData", "GreenPaletteColorLookupTableDescriptor" + instance, + 'GreenPaletteColorLookupTableData', + 'GreenPaletteColorLookupTableDescriptor' ), bluePaletteColorLookupTableData: fetchPaletteColorLookupTableData( - instance, "BluePaletteColorLookupTableData", "BluePaletteColorLookupTableDescriptor" + instance, + 'BluePaletteColorLookupTableData', + 'BluePaletteColorLookupTableDescriptor' ), }; @@ -206,8 +246,8 @@ class MetadataProvider { : [WindowWidth]; metadata = { - windowCenter: validNumber(windowCenter), - windowWidth: validNumber(windowWidth), + windowCenter: toNumber(windowCenter), + windowWidth: toNumber(windowWidth), }; break; @@ -218,8 +258,8 @@ class MetadataProvider { } metadata = { - rescaleIntercept: validNumber(instance.RescaleIntercept), - rescaleSlope: validNumber(instance.RescaleSlope), + rescaleIntercept: toNumber(instance.RescaleIntercept), + rescaleSlope: toNumber(instance.RescaleSlope), rescaleType: instance.RescaleType, }; break; @@ -331,7 +371,7 @@ class MetadataProvider { case WADO_IMAGE_LOADER_TAGS.GENERAL_IMAGE_MODULE: metadata = { sopInstanceUid: instance.SOPInstanceUID, - instanceNumber: validNumber(instance.InstanceNumber), + instanceNumber: toNumber(instance.InstanceNumber), lossyImageCompression: instance.LossyImageCompression, lossyImageCompressionRatio: instance.LossyImageCompressionRatio, lossyImageCompressionMethod: instance.LossyImageCompressionMethod, @@ -361,7 +401,14 @@ class MetadataProvider { } _getUIDsFromImageID(imageId) { - if (imageId.includes('wadors:')) { + // TODO: adding csiv here is not really correct. Probably need to use + // metadataProvider.addImageIdToUIDs(imageId, { + // StudyInstanceUID, + // SeriesInstanceUID, + // SOPInstanceUID, + // }) + // somewhere else + if (imageId.startsWith('wadors:')) { const strippedImageId = imageId.split('/studies/')[1]; const splitImageId = strippedImageId.split('/'); @@ -381,26 +428,22 @@ class MetadataProvider { } // Maybe its a non-standard imageId - return this.imageIdToUIDs.get(imageId); + // check if the imageId starts with http:// or https:// using regex + // Todo: handle non http imageIds + let imageURI; + const urlRegex = /^(http|https):\/\//; + if (urlRegex.test(imageId)) { + imageURI = imageId; + } else { + imageURI = imageIdToURI(imageId); + } + + return this.imageURIToUIDs.get(imageURI); } } const metadataProvider = new MetadataProvider(); -/** - * Returns the values as an array of javascript numbers - * - * @param element - The javascript object for the specified element in the metadata - * @returns {*} - */ -const validNumber = val => { - if (Array.isArray(val)) { - return val.map(v => (v !== undefined ? Number(v) : v)); - } else { - return val !== undefined ? Number(val) : val; - } -}; - export default metadataProvider; const WADO_IMAGE_LOADER_TAGS = { @@ -414,6 +457,7 @@ const WADO_IMAGE_LOADER_TAGS = { SOP_COMMON_MODULE: 'sopCommonModule', PET_ISOTOPE_MODULE: 'petIsotopeModule', OVERLAY_PLANE_MODULE: 'overlayPlaneModule', + PATIENT_DEMOGRAPHIC_MODULE: 'patientDemographicModule', // react-cornerstone-viewport specifc PATIENT_MODULE: 'patientModule', diff --git a/platform/core/src/defaults/hotkeyBindings.js b/platform/core/src/defaults/hotkeyBindings.js index c8fd4d446c7..2c7b5f9da8f 100644 --- a/platform/core/src/defaults/hotkeyBindings.js +++ b/platform/core/src/defaults/hotkeyBindings.js @@ -3,21 +3,52 @@ import windowLevelPresets from './windowLevelPresets'; /* * Supported Keys: https://craig.is/killing/mice */ -export default [ - { commandName: 'setToolActive', commandOptions: { toolName: 'Zoom' }, label: 'Zoom', keys: ['z'], isEditable: true }, - { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'], isEditable: true }, - { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'], isEditable: true }, - { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='], isEditable: true }, - { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'], isEditable: true }, - { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'], isEditable: true }, +const bindings = [ { - commandName: 'flipViewportVertical', + commandName: 'setToolActive', + commandOptions: { toolName: 'Zoom' }, + label: 'Zoom', + keys: ['z'], + isEditable: true, + }, + { + commandName: 'scaleUpViewport', + label: 'Zoom In', + keys: ['+'], + isEditable: true, + }, + { + commandName: 'scaleDownViewport', + label: 'Zoom Out', + keys: ['-'], + isEditable: true, + }, + { + commandName: 'fitViewportToWindow', + label: 'Zoom to Fit', + keys: ['='], + isEditable: true, + }, + { + commandName: 'rotateViewportCW', + label: 'Rotate Right', + keys: ['r'], + isEditable: true, + }, + { + commandName: 'rotateViewportCCW', + label: 'Rotate Left', + keys: ['l'], + isEditable: true, + }, + { + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], isEditable: true, }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], isEditable: true, @@ -57,11 +88,36 @@ export default [ keys: ['pagedown'], isEditable: true, }, - { commandName: 'nextImage', label: 'Next Image', keys: ['down'], isEditable: true }, - { commandName: 'previousImage', label: 'Previous Image', keys: ['up'], isEditable: true }, - { commandName: 'firstImage', label: 'First Image', keys: ['home'], isEditable: true }, - { commandName: 'lastImage', label: 'Last Image', keys: ['end'], isEditable: true }, - { commandName: 'resetViewport', label: 'Reset', keys: ['space'], isEditable: true }, + { + commandName: 'nextImage', + label: 'Next Image', + keys: ['down'], + isEditable: true, + }, + { + commandName: 'previousImage', + label: 'Previous Image', + keys: ['up'], + isEditable: true, + }, + { + commandName: 'firstImage', + label: 'First Image', + keys: ['home'], + isEditable: true, + }, + { + commandName: 'lastImage', + label: 'Last Image', + keys: ['end'], + isEditable: true, + }, + { + commandName: 'resetViewport', + label: 'Reset', + keys: ['space'], + isEditable: true, + }, { commandName: 'cancelMeasurement', label: 'Cancel Cornerstone Measurement', @@ -122,3 +178,5 @@ export default [ keys: ['9'], }, ]; + +export default bindings; diff --git a/platform/core/src/extensions/ExtensionManager.js b/platform/core/src/extensions/ExtensionManager.js index 439f4f64ba3..38583497d72 100644 --- a/platform/core/src/extensions/ExtensionManager.js +++ b/platform/core/src/extensions/ExtensionManager.js @@ -99,17 +99,28 @@ export default class ExtensionManager { * * @param {Object[]} extensions - Array of extensions */ - registerExtensions = (extensions, dataSources = []) => { - extensions.forEach(extension => { + registerExtensions = async (extensions, dataSources = []) => { + // Todo: we ideally should be able to run registrations in parallel + // but currently since some extensions need to be registered before + // others, we need to run them sequentially. We need a postInit hook + // to avoid this sequential async registration + for (const extension of extensions) { const hasConfiguration = Array.isArray(extension); - - if (hasConfiguration) { - const [ohifExtension, configuration] = extension; - this.registerExtension(ohifExtension, configuration, dataSources); - } else { - this.registerExtension(extension, {}, dataSources); + try { + if (hasConfiguration) { + const [ohifExtension, configuration] = extension; + await this.registerExtension( + ohifExtension, + configuration, + dataSources + ); + } else { + await this.registerExtension(extension, {}, dataSources); + } + } catch (error) { + console.error(error); } - }); + } }; /** @@ -118,7 +129,11 @@ export default class ExtensionManager { * @param {Object} extension * @param {Object} configuration */ - registerExtension = (extension, configuration = {}, dataSources = []) => { + registerExtension = async ( + extension, + configuration = {}, + dataSources = [] + ) => { if (!extension) { throw new Error('Attempting to register a null/undefined extension.'); } @@ -140,10 +155,11 @@ export default class ExtensionManager { // preRegistrationHook if (extension.preRegistration) { - extension.preRegistration({ + await extension.preRegistration({ servicesManager: this._servicesManager, commandsManager: this._commandsManager, hotkeysManager: this._hotkeysManager, + extensionManager: this, appConfig: this._appConfig, configuration, }); @@ -186,6 +202,7 @@ export default class ExtensionManager { case MODULE_TYPES.SOP_CLASS_HANDLER: case MODULE_TYPES.CONTEXT: case MODULE_TYPES.LAYOUT_TEMPLATE: + case MODULE_TYPES.UTILITY: case MODULE_TYPES.HANGING_PROTOCOL: // Default for most extension points, // Just adds each entry ready for consumption by mode. @@ -299,14 +316,6 @@ export default class ExtensionManager { }); } - _initHangingProtocolModule(extensionModule, extensionId) { - extensionModule.forEach(element => { - this.modulesMap[ - `${extensionId}.${MODULE_TYPES.HANGING_PROTOCOL}.${element.name}` - ] = element; - }); - } - /** * * @private diff --git a/platform/core/src/extensions/ExtensionManager.test.js b/platform/core/src/extensions/ExtensionManager.test.js index 6606105ed4a..8bc41e75523 100644 --- a/platform/core/src/extensions/ExtensionManager.test.js +++ b/platform/core/src/extensions/ExtensionManager.test.js @@ -40,18 +40,18 @@ describe('ExtensionManager.js', () => { }); describe('registerExtensions()', () => { - it('calls registerExtension() for each extension', () => { + it('calls registerExtension() for each extension', async () => { extensionManager.registerExtension = jest.fn(); // SUT const fakeExtensions = [{ one: '1' }, { two: '2' }, { three: '3 ' }]; - extensionManager.registerExtensions(fakeExtensions); + await extensionManager.registerExtensions(fakeExtensions); // Assert expect(extensionManager.registerExtension.mock.calls.length).toBe(3); }); - it('calls registerExtension() for each extension passing its configuration if tuple', () => { + it('calls registerExtension() for each extension passing its configuration if tuple', async () => { const fakeConfiguration = { testing: true }; extensionManager.registerExtension = jest.fn(); @@ -61,7 +61,7 @@ describe('ExtensionManager.js', () => { [{ two: '2' }, fakeConfiguration], { three: '3 ' }, ]; - extensionManager.registerExtensions(fakeExtensions); + await extensionManager.registerExtensions(fakeExtensions); // Assert expect(extensionManager.registerExtension.mock.calls[1][1]).toEqual( @@ -91,30 +91,35 @@ describe('ExtensionManager.js', () => { expect(extension.preRegistration.mock.calls[0][0]).toEqual({ servicesManager, commandsManager, + extensionManager, appConfig, configuration: extensionConfiguration, }); }); - it('logs a warning if the extension is null or undefined', () => { + it('logs a warning if the extension is null or undefined', async () => { const undefinedExtension = undefined; const nullExtension = null; - expect(() => { - extensionManager.registerExtension(undefinedExtension); - }).toThrow('Attempting to register a null/undefined extension.'); + await expect( + extensionManager.registerExtension(undefinedExtension) + ).rejects.toThrow( + new Error('Attempting to register a null/undefined extension.') + ); - expect(() => { - extensionManager.registerExtension(nullExtension); - }).toThrow('Attempting to register a null/undefined extension.'); + await expect( + extensionManager.registerExtension(nullExtension) + ).rejects.toThrow( + new Error('Attempting to register a null/undefined extension.') + ); }); - it('logs a warning if the extension does not have an id', () => { + it('logs a warning if the extension does not have an id', async () => { const extensionWithoutId = {}; - expect(() => { - extensionManager.registerExtension(extensionWithoutId); - }).toThrow(new Error('Extension ID not set')); + await expect( + extensionManager.registerExtension(extensionWithoutId) + ).rejects.toThrow(new Error('Extension ID not set')); }); it('tracks which extensions have been registered', () => { @@ -153,7 +158,7 @@ describe('ExtensionManager.js', () => { ); }); - it('logs an error if an exception is thrown while retrieving a module', () => { + it('logs an error if an exception is thrown while retrieving a module', async () => { const extensionWithBadModule = { id: 'hello-world', getViewportModule: () => { @@ -161,9 +166,9 @@ describe('ExtensionManager.js', () => { }, }; - expect(() => { - extensionManager.registerExtension(extensionWithBadModule); - }).toThrow(); + await expect( + extensionManager.registerExtension(extensionWithBadModule) + ).rejects.toThrow(); }); it('successfully passes dependencies to each module along with extension configuration', () => { @@ -194,7 +199,7 @@ describe('ExtensionManager.js', () => { }); }); - it('successfully registers a module for each module type', () => { + it('successfully registers a module for each module type', async () => { const extension = { id: 'hello-world', getViewportModule: () => { @@ -224,9 +229,12 @@ describe('ExtensionManager.js', () => { getContextModule: () => { return [{}]; }, + getUtilityModule: () => { + return [{}]; + }, }; - extensionManager.registerExtension(extension); + await extensionManager.registerExtension(extension); // Registers 1 module per module type Object.keys(extensionManager.modules).forEach(moduleType => { diff --git a/platform/core/src/extensions/MODULE_TYPES.js b/platform/core/src/extensions/MODULE_TYPES.js index d77cced1628..a0627c9baa0 100644 --- a/platform/core/src/extensions/MODULE_TYPES.js +++ b/platform/core/src/extensions/MODULE_TYPES.js @@ -8,4 +8,5 @@ export default { CONTEXT: 'contextModule', LAYOUT_TEMPLATE: 'layoutTemplateModule', HANGING_PROTOCOL: 'hangingProtocolModule', + UTILITY: 'utilityModule', }; diff --git a/platform/core/src/index.js b/platform/core/src/index.js index 49ccebbfa4f..c45e3c6f25e 100644 --- a/platform/core/src/index.js +++ b/platform/core/src/index.js @@ -3,7 +3,6 @@ import { ServicesManager } from './services'; import classes, { CommandsManager, HotkeysManager } from './classes/'; import DICOMWeb from './DICOMWeb'; -import DICOMSR from './DICOMSR'; import errorHandler from './errorHandler.js'; import log from './log.js'; import object from './object.js'; @@ -27,6 +26,7 @@ import { HangingProtocolService, pubSubServiceInterface, UserAuthenticationService, + SegmentationService, } from './services'; import IWebApiDataSource from './DataSources/IWebApiDataSource'; @@ -54,7 +54,6 @@ const OHIF = { object, log, DICOMWeb, - DICOMSR, viewer: {}, // CineService, @@ -68,6 +67,7 @@ const OHIF = { ViewportGridService, HangingProtocolService, UserAuthenticationService, + SegmentationService, IWebApiDataSource, DicomMetadataStore, pubSubServiceInterface, @@ -91,7 +91,6 @@ export { object, log, DICOMWeb, - DICOMSR, // CineService, UIDialogService, @@ -104,6 +103,7 @@ export { ViewportGridService, HangingProtocolService, UserAuthenticationService, + SegmentationService, IWebApiDataSource, DicomMetadataStore, pubSubServiceInterface, diff --git a/platform/core/src/index.test.js b/platform/core/src/index.test.js index 1ae71de1de0..955c599e2a9 100644 --- a/platform/core/src/index.test.js +++ b/platform/core/src/index.test.js @@ -21,7 +21,6 @@ describe('Top level exports', () => { 'object', 'log', 'DICOMWeb', - 'DICOMSR', 'OHIF', // 'CineService', @@ -33,6 +32,7 @@ describe('Top level exports', () => { 'MeasurementService', 'ToolBarService', 'ViewportGridService', + 'SegmentationService', 'HangingProtocolService', 'UserAuthenticationService', 'IWebApiDataSource', diff --git a/platform/core/src/services/CineService/CineService.js b/platform/core/src/services/CineService/CineService.js index 4060ea864a7..ac3600d8b83 100644 --- a/platform/core/src/services/CineService/CineService.js +++ b/platform/core/src/services/CineService/CineService.js @@ -5,15 +5,17 @@ const publicAPI = { getState: _getState, setCine: _setCine, setIsCineEnabled: _setIsCineEnabled, + playClip: _playClip, + stopClip: _stopClip, setServiceImplementation, }; const serviceImplementation = { _getState: () => console.warn('getState() NOT IMPLEMENTED'), - _setCine: () => - console.warn('setCine() NOT IMPLEMENTED'), - _setIsCineEnabled: () => - console.warn('setIsCineEnabled() NOT IMPLEMENTED'), + _setCine: () => console.warn('setCine() NOT IMPLEMENTED'), + _playClip: () => console.warn('playClip() NOT IMPLEMENTED'), + _stopClip: () => console.warn('stopClip() NOT IMPLEMENTED'), + _setIsCineEnabled: () => console.warn('setIsCineEnabled() NOT IMPLEMENTED'), }; function _getState() { @@ -28,10 +30,20 @@ function _setIsCineEnabled(isCineEnabled) { return serviceImplementation._setIsCineEnabled(isCineEnabled); } +function _playClip(element, playClipOptions) { + return serviceImplementation._playClip(element, playClipOptions); +} + +function _stopClip(element) { + return serviceImplementation._stopClip(element); +} + function setServiceImplementation({ getState: getStateImplementation, setCine: setCineImplementation, setIsCineEnabled: setIsCineEnabledImplementation, + playClip: playClipImplementation, + stopClip: stopClipImplementation, }) { if (getStateImplementation) { serviceImplementation._getState = getStateImplementation; @@ -42,6 +54,14 @@ function setServiceImplementation({ if (setIsCineEnabledImplementation) { serviceImplementation._setIsCineEnabled = setIsCineEnabledImplementation; } + + if (playClipImplementation) { + serviceImplementation._playClip = playClipImplementation; + } + + if (stopClipImplementation) { + serviceImplementation._stopClip = stopClipImplementation; + } } export default { diff --git a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js index 26f2884dadf..5380dd726ff 100644 --- a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js +++ b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js @@ -1,33 +1,56 @@ -import dcmjs from 'dcmjs' +import dcmjs from 'dcmjs'; import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; import createStudyMetadata from './createStudyMetadata'; -import EVENTS from './EVENTS'; +const EVENTS = { + STUDY_ADDED: 'event::dicomMetadataStore:studyAdded', + INSTANCES_ADDED: 'event::dicomMetadataStore:instancesAdded', + SERIES_ADDED: 'event::dicomMetadataStore:seriesAdded', + SERIES_UPDATED: 'event::dicomMetadataStore:seriesUpdated', +}; + +/** + * @example + * studies: [ + * { + * StudyInstanceUID: string, + * isLoaded: boolean, + * series: [ + * { + * Modality: string, + * SeriesInstanceUID: string, + * SeriesNumber: number, + * SeriesDescription: string, + * instances: [ + * { + * // naturalized instance metadata + * SOPInstanceUID: string, + * SOPClassUID: string, + * Rows: number, + * Columns: number, + * PatientSex: string, + * Modality: string, + * InstanceNumber: string, + * }, + * { + * // instance 2 + * }, + * ], + * }, + * { + * // series 2 + * }, + * ], + * }, + * ], + */ const _model = { studies: [], - // studies: [{ - // seriesLists: [ - // { - // // Series in study from dicom web server 1 (or different backend 1) - // series: [{ - // instances: [{ - // ...instanceMetadata // Naturalized DICOM. - // }], - // ...seriesMetadata - // }], - // clientName - // }, - // { - // // Series in study from dicom web server 2 (or different backend 2) - // }, - // ], - // ...studyMetadata, - // }] }; function _getStudyInstanceUIDs() { - return _model.studies.map(aStudy => aStudy.StudyInstanceUID) + return _model.studies.map(aStudy => aStudy.StudyInstanceUID); } function _getStudy(StudyInstanceUID) { @@ -60,7 +83,7 @@ function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { ); } -function _getInstanceFromImageId(imageId) { +function _getInstanceByImageId(imageId) { for (let study of _model.studies) { for (let series of study.series) { for (let instance of series.instances) { @@ -72,6 +95,52 @@ function _getInstanceFromImageId(imageId) { } } +/** + * Update the metadata of a specific series + * @param {*} StudyInstanceUID + * @param {*} SeriesInstanceUID + * @param {*} metadata metadata inform of key value pairs + * @returns + */ +function _updateMetadataForSeries( + StudyInstanceUID, + SeriesInstanceUID, + metadata +) { + const study = _getStudy(StudyInstanceUID); + + if (!study) { + return; + } + + const series = study.series.find( + aSeries => aSeries.SeriesInstanceUID === SeriesInstanceUID + ); + + const { instances } = series; + // update all instances metadata for this series with the new metadata + instances.forEach(instance => { + Object.keys(metadata).forEach(key => { + // if metadata[key] is an object, we need to merge it with the existing + // metadata of the instance + if (typeof metadata[key] === 'object') { + instance[key] = { ...instance[key], ...metadata[key] }; + } + // otherwise, we just replace the existing metadata with the new one + else { + instance[key] = metadata[key]; + } + }); + }); + + // broadcast the series updated event + this._broadcastEvent(EVENTS.SERIES_UPDATED, { + SeriesInstanceUID, + StudyInstanceUID, + madeInClient: true, + }); +} + const BaseImplementation = { EVENTS, listeners: {}, @@ -80,7 +149,9 @@ const BaseImplementation = { // If Arraybuffer, parse to DICOMJSON before naturalizing. if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) { - const dicomData = dcmjs.data.DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer); + const dicomData = dcmjs.data.DicomMessage.readFile( + dicomJSONDatasetOrP10ArrayBuffer + ); dicomJSONDataset = dicomData.dict; } else { @@ -140,6 +211,7 @@ const BaseImplementation = { let study = _getStudy(StudyInstanceUID); if (!study) { study = createStudyMetadata(StudyInstanceUID); + study.StudyDescription = seriesSummaryMetadata[0].StudyDescription; _model.studies.push(study); } @@ -179,15 +251,18 @@ const BaseImplementation = { getStudy: _getStudy, getSeries: _getSeries, getInstance: _getInstance, - getInstanceFromImageId: _getInstanceFromImageId, + getInstanceByImageId: _getInstanceByImageId, + updateMetadataForSeries: _updateMetadataForSeries, }; - const DicomMetadataStore = Object.assign( + // get study + + // iterate over all series + {}, BaseImplementation, pubSubServiceInterface ); - export { DicomMetadataStore }; export default DicomMetadataStore; diff --git a/platform/core/src/services/DicomMetadataStore/EVENTS.js b/platform/core/src/services/DicomMetadataStore/EVENTS.js deleted file mode 100644 index 09c1475da6e..00000000000 --- a/platform/core/src/services/DicomMetadataStore/EVENTS.js +++ /dev/null @@ -1,7 +0,0 @@ -const EVENTS = { - STUDY_ADDED: 'event::dicomMetadataStore:studyAdded', - INSTANCES_ADDED: 'event::dicomMetadataStore:instancesAdded', - SERIES_ADDED: 'event::dicomMetadataStore:seriesAdded', -}; - -export default EVENTS; diff --git a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js index e3657e51eca..bb72dc14055 100644 --- a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js +++ b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js @@ -3,6 +3,7 @@ import createSeriesMetadata from './createSeriesMetadata'; function createStudyMetadata(StudyInstanceUID) { return { StudyInstanceUID, + StudyDescription: '', isLoaded: false, series: [], /** @@ -10,7 +11,7 @@ function createStudyMetadata(StudyInstanceUID) { * @param {object} instance * @returns {bool} true if series were added; false if series already exist */ - addInstanceToSeries: function (instance) { + addInstanceToSeries: function(instance) { const { SeriesInstanceUID } = instance; const existingSeries = this.series.find( s => s.SeriesInstanceUID === SeriesInstanceUID @@ -29,7 +30,7 @@ function createStudyMetadata(StudyInstanceUID) { * @param {string} instances[].SeriesInstanceUID * @returns {bool} true if series were added; false if series already exist */ - addInstancesToSeries: function (instances) { + addInstancesToSeries: function(instances) { const { SeriesInstanceUID } = instances[0]; const existingSeries = this.series.find( s => s.SeriesInstanceUID === SeriesInstanceUID @@ -43,7 +44,7 @@ function createStudyMetadata(StudyInstanceUID) { } }, - setSeriesMetadata: function (SeriesInstanceUID, seriesMetadata) { + setSeriesMetadata: function(SeriesInstanceUID, seriesMetadata) { let existingSeries = this.series.find( s => s.SeriesInstanceUID === SeriesInstanceUID ); diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.js b/platform/core/src/services/DisplaySetService/DisplaySetService.js index f6610c8625b..7a5ec81584e 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.js +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.js @@ -26,7 +26,7 @@ const findInstance = (instance, displaySets) => { if (findInSet(instance, displayset.others)) return true; } return false; -} +}; export default class DisplaySetService { constructor() { @@ -89,6 +89,20 @@ export default class DisplaySetService { return displaySet; } + setDisplaySetMetadataInvalidated(displaySetInstanceUID) { + const displaySet = this.getDisplaySetByUID(displaySetInstanceUID); + + if (!displaySet) { + return; + } + + // broadcast event to update listeners with the new displaySets + this._broadcastEvent( + EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, + displaySetInstanceUID + ); + } + deleteDisplaySet(displaySetInstanceUID) { const { activeDisplaySets } = this; @@ -164,7 +178,7 @@ export default class DisplaySetService { } // TODO: This is tricky. How do we know we're not resetting to the same/existing DSs? - // TODO: This is likely run anytime we touch DicomMetadataStore. How do we prevent uneccessary broadcasts? + // TODO: This is likely run anytime we touch DicomMetadataStore. How do we prevent unnecessary broadcasts? if (displaySetsAdded && displaySetsAdded.length) { this._broadcastEvent(EVENTS.DISPLAY_SETS_CHANGED, this.activeDisplaySets); this._broadcastEvent(EVENTS.DISPLAY_SETS_ADDED, { @@ -213,10 +227,14 @@ export default class DisplaySetService { this._addDisplaySetsToCache(displaySets); this._addActiveDisplaySets(displaySets); - instances = instances.filter(instance => !findInstance(instance, displaySets)) + instances = instances.filter( + instance => !findInstance(instance, displaySets) + ); } - allDisplaySets = allDisplaySets ? [...allDisplaySets, ...displaySets] : displaySets; + allDisplaySets = allDisplaySets + ? [...allDisplaySets, ...displaySets] + : displaySets; if (!instances.length) return allDisplaySets; } diff --git a/platform/core/src/services/DisplaySetService/EVENTS.js b/platform/core/src/services/DisplaySetService/EVENTS.js index 44e07d1f650..445736a1230 100644 --- a/platform/core/src/services/DisplaySetService/EVENTS.js +++ b/platform/core/src/services/DisplaySetService/EVENTS.js @@ -2,6 +2,8 @@ const EVENTS = { DISPLAY_SETS_ADDED: 'event::displaySetService:displaySetsAdded', DISPLAY_SETS_CHANGED: 'event::displaySetService:displaySetsChanged', DISPLAY_SETS_REMOVED: 'event::displaySetService:displaySetsRemoved', + DISPLAY_SET_SERIES_METADATA_INVALIDATED: + 'event::displaySetService:displaySetSeriesMetadataInvalidated', }; export default EVENTS; diff --git a/platform/core/src/services/HangingProtocolService/HPMatcher.js b/platform/core/src/services/HangingProtocolService/HPMatcher.js index 29b0e4a1c58..08b66daaf82 100644 --- a/platform/core/src/services/HangingProtocolService/HPMatcher.js +++ b/platform/core/src/services/HangingProtocolService/HPMatcher.js @@ -1,6 +1,5 @@ import validate from './lib/validator'; - /** * Match a Metadata instance against rules using Validate.js for validation. * @param {InstanceMetadata} metadataInstance Metadata instance object @@ -36,7 +35,13 @@ const match = (metadataInstance, rules, customAttributeRetrievalCallbacks) => { // Create a single attribute object to be validated, since metadataInstance is an // instance of Metadata (StudyMetadata, SeriesMetadata or InstanceMetadata) - const attributeValue = metadataInstance[attribute]; + let attributeValue = metadataInstance[attribute]; + if (attributeValue === undefined) { + if (attribute === 'NumberOfStudyRelatedSeries') { + attributeValue = metadataInstance.series?.length; + } + // Add other computable values such as modalities in study + } const attributeMap = { [attribute]: attributeValue, }; diff --git a/platform/core/src/services/HangingProtocolService/HangingProtocolService.js b/platform/core/src/services/HangingProtocolService/HangingProtocolService.js index d00dcb15e72..1af9cc05ef7 100644 --- a/platform/core/src/services/HangingProtocolService/HangingProtocolService.js +++ b/platform/core/src/services/HangingProtocolService/HangingProtocolService.js @@ -1,4 +1,3 @@ -import cloneDeep from 'lodash.clonedeep'; import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; import sortBy from '../../utils/sortBy.js'; import ProtocolEngine from './ProtocolEngine'; @@ -6,11 +5,8 @@ import ProtocolEngine from './ProtocolEngine'; const EVENTS = { STAGE_CHANGE: 'event::hanging_protocol_stage_change', NEW_LAYOUT: 'event::hanging_protocol_new_layout', -}; - -const VIEWPORT_SETTING_TYPES = { - PROPS: 'props', - VIEWPORT: 'viewport', + CUSTOM_IMAGE_LOAD_PERFORMED: + 'event::hanging_protocol_custom_image_load_performed', }; class HangingProtocolService { @@ -20,12 +16,38 @@ class HangingProtocolService { this.ProtocolEngine = undefined; this.protocol = undefined; this.stage = undefined; + /** + * An array that contains for each viewport (viewportIndex) specified in the + * hanging protocol, an object of the form + * + * { + * viewportOptions, + * displaySetsInfo, // contains array of [ { SeriesInstanceUID, displaySetOPtions}, ... ] + * } + */ this.matchDetails = []; + /** + * displaySetMatchDetails = + * DisplaySetId is the id defined in the hangingProtocol + * match is an object that contains information about + * + * { + * SeriesInstanceUID, + * StudyInstanceUID, + * matchDetails, + * matchingScore, + * sortingInfo + * } + */ + this.displaySetMatchDetails = new Map(); this.hpAlreadyApplied = []; this.studies = []; this.customViewportSettings = []; this.customAttributeRetrievalCallbacks = {}; this.listeners = {}; + this.registeredImageLoadStrategies = {}; + this.activeImageLoadStrategyName = null; + this.customImageLoadPerformed = false; Object.defineProperty(this, 'EVENTS', { value: EVENTS, writable: false, @@ -43,6 +65,10 @@ class HangingProtocolService { // this.ProtocolEngine.reset() } + getDisplaySetsMatchDetails() { + return this.displaySetMatchDetails; + } + getState() { return [this.matchDetails, this.hpAlreadyApplied]; } @@ -54,7 +80,7 @@ class HangingProtocolService { addProtocols(protocols) { protocols.forEach(protocol => { if (this.protocols.indexOf(protocol) === -1) { - this.protocols.push(protocol); + this.protocols.push(this._validateProtocol(protocol)); } }); } @@ -81,20 +107,37 @@ class HangingProtocolService { this._setProtocol(protocol); } - setHangingProtocolAppliedForViewport(i) { - this.hpAlreadyApplied[i] = true; + /** + * Returns true, if the hangingProtocol has a custom loading strategy for the images + * and its callback has been added to the HangingProtocolService + * @returns {boolean} true + */ + hasCustomImageLoadStrategy() { + return ( + this.activeImageLoadStrategyName !== null && + this.registeredImageLoadStrategies[ + this.activeImageLoadStrategyName + ] instanceof Function + ); + } + + getCustomImageLoadPerformed() { + return this.customImageLoadPerformed; } /** - * Adds a custom setting that can be chosen in the HangingProtocol UI and applied to a Viewport - * - * @param settingId The ID used to refer to the setting (e.g. 'displayCADMarkers') - * @param settingName The name of the setting to be displayed (e.g. 'Display CAD Markers') - * @param options - * @param callback A function to be run after a viewport is rendered with a series + * Set the strategy callback for loading images to the HangingProtocolService + * @param {string} name strategy name + * @param {Function} callback image loader callback */ - addCustomViewportSetting(...params) { - this.customViewportSettings.push(...params); + registerImageLoadStrategy(name, callback) { + if (callback instanceof Function && name) { + this.registeredImageLoadStrategies[name] = callback; + } + } + + setHangingProtocolAppliedForViewport(i) { + this.hpAlreadyApplied[i] = true; } /** @@ -134,12 +177,82 @@ class HangingProtocolService { } } + /** + * Executes the callback function for the custom loading strategy for the images + * if no strategy is set, the default strategy is used + */ + runImageLoadStrategy(data) { + const loader = this.registeredImageLoadStrategies[ + this.activeImageLoadStrategyName + ]; + const loadedData = loader({ + data, + displaySetsMatchDetails: this.getDisplaySetsMatchDetails(), + matchDetails: this.matchDetails, + }); + + // if loader successfully re-arranged the data with the custom strategy + // and returned the new props, then broadcast them + if (!loadedData) { + return; + } + + this.customImageLoadPerformed = true; + this._broadcastChange(this.EVENTS.CUSTOM_IMAGE_LOAD_PERFORMED, loadedData); + } + + _validateProtocol(protocol) { + protocol.id = protocol.id || protocol.name; + // Automatically compute some number of attributes if they + // aren't present. Makes defining new HPs easier. + protocol.name = protocol.name || protocol.id; + const { stages } = protocol; + + // Generate viewports automatically as required. + stages.forEach(stage => { + if (!stage.viewports) { + stage.viewports = []; + const { rows, columns } = stage.viewportStructure.properties; + + for (let i = 0; i < rows * columns; i++) { + stage.viewports.push({ + viewportOptions: {}, + displaySets: [], + }); + } + } else { + stage.viewports.forEach(viewport => { + viewport.viewportOptions = viewport.viewportOptions || {}; + if (!viewport.displaySets) { + viewport.displaySets = []; + } else { + viewport.displaySets.forEach(displaySet => { + displaySet.options = displaySet.options || {}; + }); + } + }); + } + }); + + return protocol; + } + _setProtocol(protocol) { // TODO: Add proper Protocol class to validate the protocols // which are entered manually this.stage = 0; this.protocol = protocol; - this._updateViewports(protocol); + const { imageLoadStrategy } = protocol; + if (imageLoadStrategy) { + // check if the imageLoadStrategy is a valid strategy + if ( + this.registeredImageLoadStrategies[imageLoadStrategy] instanceof + Function + ) { + this.activeImageLoadStrategyName = imageLoadStrategy; + } + } + this._updateViewports(); } /** @@ -173,6 +286,9 @@ class HangingProtocolService { return; } + // reset displaySetMatchDetails + this.displaySetMatchDetails = new Map(); + // Retrieve the current stage const stageModel = this._getCurrentStageModel(); @@ -182,18 +298,14 @@ class HangingProtocolService { !stageModel || !stageModel.viewportStructure || !stageModel.viewports || + !stageModel.displaySets || !stageModel.viewports.length ) { return; } - // Retrieve the layoutTemplate associated with the current display set's viewport structure - // If no such template name exists, stop here. - // const layoutTemplateName = stageModel.viewportStructure.getLayoutTemplateName(); - const layoutTemplateName = 'gridLayout'; - if (!layoutTemplateName) { - return; - } + this.customImageLoadPerformed = false; + const { type: layoutType } = stageModel.viewportStructure; // Retrieve the properties associated with the current display set's viewport structure template // If no such layout properties exist, stop here. @@ -202,97 +314,70 @@ class HangingProtocolService { return; } - const { columns: numCols, rows: numRows } = layoutProps; + const { columns: numCols, rows: numRows, layoutOptions = [] } = layoutProps; + this._broadcastChange(this.EVENTS.NEW_LAYOUT, { + layoutType, numRows, numCols, + layoutOptions, }); - // Empty the matchDetails associated with the ProtocolEngine. - // This will be used to store the pass/fail details and score - // for each of the viewport matching procedures + // Matching the displaySets + // Note: this is happening before displaySets are created. Here, displaySet + // only contains the information of the id of the displaySet to be matched + // based on some rules + stageModel.displaySets.forEach(displaySet => { + const { bestMatch } = this._matchImages(displaySet); + this.displaySetMatchDetails.set(displaySet.id, bestMatch); + }); // Loop through each viewport stageModel.viewports.forEach((viewport, viewportIndex) => { + const { viewportOptions } = viewport; this.hpAlreadyApplied.push(false); - const details = this._matchImages(viewport); - let currentMatch = details.bestMatch; + // DisplaySets for the viewport, Note: this is not the actual displaySet, + // but it is a info to locate the displaySet from the displaySetService + let displaySetsInfo = []; + viewport.displaySets.forEach(({ id, options: displaySetOptions }) => { + const viewportDisplaySet = this.displaySetMatchDetails.get(id); + + if (viewportDisplaySet) { + const { SeriesInstanceUID } = viewportDisplaySet; + + const displaySetInfo = { + SeriesInstanceUID, + displaySetOptions, + }; + + displaySetsInfo.push(displaySetInfo); + } else { + console.warn( + ` + The hanging protocol viewport is requesting to display ${id} displaySet that is not + matched based on the provided criteria (e.g. matching rules). + ` + ); + } + }); - const currentViewportData = { - viewportIndex, - SeriesInstanceUID: currentMatch && currentMatch.SeriesInstanceUID, + this.matchDetails[viewportIndex] = { + viewportOptions, + displaySetsInfo, }; - - // Viewport Settings - // - // protocol defined callback - const protocolCallbacks = viewport.viewportSettings.filter( - setting => setting.type === VIEWPORT_SETTING_TYPES.PROPS - ); - // manually added callback - const customCallbacks = this.customViewportSettings.filter( - setting => setting.type === VIEWPORT_SETTING_TYPES.PROPS - ); - const callbacks = protocolCallbacks.concat(customCallbacks); - - // if we have callbacks to applied at the app level or at the HP level - if (callbacks.length) { - currentViewportData.renderedCallback = (element, ToolBarService) => { - callbacks.forEach(setting => { - const { commandName, options } = setting; - options.viewportIndex = viewportIndex; - options.element = element; - // Toolbar service to handle tool activation - if (commandName === 'setToolActive') { - ToolBarService.recordInteraction(options); - return; - } - // other commands - this._commandsManager.runCommand(commandName, options); - }); - }; - } - - // initial viewport settings defined by protocol - const protocolInitialViewport = viewport.viewportSettings.filter( - setting => setting.type === VIEWPORT_SETTING_TYPES.VIEWPORT - ); - // custom added initial viewport settings - const customInitialViewport = this.customViewportSettings.filter( - setting => setting.type === VIEWPORT_SETTING_TYPES.VIEWPORT - ); - // TODO: conflict might happen between protocol and custom viewport settings - const viewportSettings = protocolInitialViewport.concat( - customInitialViewport - ); - - if (viewportSettings.length) { - const initialViewport = {}; - viewportSettings.forEach(setting => { - const { options } = setting; - if (!options) return; - // Do not manipulate the hp settings - const viewportOptions = cloneDeep(options); - Object.entries(viewportOptions).forEach(([key, value]) => { - initialViewport[key] = value; - }); - }); - currentViewportData.initialViewport = initialViewport; - } - - this.matchDetails[viewportIndex] = currentViewportData; }); } // Match images given a list of Studies and a Viewport's image matching reqs - _matchImages(viewport) { + _matchImages(displaySet) { console.log('ProtocolEngine::matchImages'); // TODO: matching is applied on study and series level, instance // level matching needs to be added in future - const { studyMatchingRules, seriesMatchingRules } = viewport; + // Todo: handle fusion viewports by not taking the first displaySet rule for the viewport + const { studyMatchingRules, seriesMatchingRules } = displaySet; const matchingScores = []; let highestStudyMatchingScore = 0; @@ -460,6 +545,7 @@ class HangingProtocolService { * within the measurement service and the source needs to update. * @return void */ + // Todo: why do we have a separate broadcastChange function here? _broadcastChange(eventName, eventData) { const hasListeners = Object.keys(this.listeners).length > 0; const hasCallbacks = Array.isArray(this.listeners[eventName]); diff --git a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js index 32d5c253c49..7649c2e5d82 100644 --- a/platform/core/src/services/HangingProtocolService/ProtocolEngine.js +++ b/platform/core/src/services/HangingProtocolService/ProtocolEngine.js @@ -1,41 +1,6 @@ import { HPMatcher } from './HPMatcher.js'; import { sortByScore } from './lib/sortByScore'; -const deafultProtocol = { - id: 'defaultProtocol', - locked: true, - hasUpdatedPriorsInformation: false, - name: 'Default', - createdDate: '2021-02-23T19:22:08.894Z', - modifiedDate: '2021-02-23T19:22:08.894Z', - availableTo: {}, - editableBy: {}, - protocolMatchingRules: [], - stages: [ - { - id: 'nwzau7jDkEkL8djfr', - name: 'oneByOne', - viewportStructure: { - type: 'grid', - properties: { - rows: 1, - columns: 1, - }, - }, - viewports: [ - { - viewportSettings: [], - imageMatchingRules: [], - seriesMatchingRules: [], - studyMatchingRules: [], - }, - ], - createdDate: '2021-02-23T19:22:08.894Z', - }, - ], - numberOfPriorsReferenced: -1, -}; - export default class ProtocolEngine { constructor(protocols, customAttributeRetrievalCallbacks) { this.protocols = protocols; @@ -117,7 +82,7 @@ export default class ProtocolEngine { /** * Finds the best protocols from Protocol Store, matching each protocol matching rules - * with the given study. The best protocol are orded by score and returned in an array + * with the given study. The best protocol are ordered by score and returned in an array * @param {Object} study StudyMetadata instance object * @return {Array} Array of match objects or an empty array if no match was found * Each match object has the score of the matching and the matched @@ -131,7 +96,10 @@ export default class ProtocolEngine { // We clone it so that we don't accidentally add the // numberOfPriorsReferenced rule to the Protocol itself. let rules = protocol.protocolMatchingRules.slice(); - if (!rules) { + if (!rules || !rules.length) { + console.warn( + 'ProtocolEngine::findMatchByStudy no matching rules - specify protocolMatchingRules' + ); return; } @@ -153,7 +121,7 @@ export default class ProtocolEngine { return [ { score: 1, - protocol: deafultProtocol, + protocol: this.protocols.find(protocol => protocol.id === 'default'), }, ]; } diff --git a/platform/core/src/services/MeasurementService/MeasurementService.js b/platform/core/src/services/MeasurementService/MeasurementService.js index 292d99134c2..cfb0a85fb3d 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.js @@ -15,7 +15,7 @@ import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; * Measurement schema * * @typedef {Object} Measurement - * @property {number} id - + * @property {number} uid - * @property {string} SOPInstanceUID - * @property {string} FrameOfReferenceUID - * @property {string} referenceSeriesUID - @@ -30,24 +30,31 @@ import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; /* Measurement schema keys for object validation. */ const MEASUREMENT_SCHEMA_KEYS = [ - 'id', + 'uid', + 'data', + 'getReport', + 'displayText', 'SOPInstanceUID', 'FrameOfReferenceUID', 'referenceStudyUID', 'referenceSeriesUID', + 'displaySetInstanceUID', 'label', 'description', 'type', 'unit', + 'points', + 'source', + 'toolName', + 'metadata', + // Todo: we shouldn't need to have all these here. 'area', // TODO: Add concept names instead (descriptor) 'mean', 'stdDev', 'length', 'shortestDiameter', 'longestDiameter', - 'text', // NOTE: There is nothing like this in SR. - 'points', - 'source', + 'cachedStats', ]; const EVENTS = { @@ -65,10 +72,25 @@ const VALUE_TYPES = { POINT: 'value_type::point', BIDIRECTIONAL: 'value_type::shortAxisLongAxis', // TODO -> Discuss with Danny. => just using SCOORD values isn't enough here. ELLIPSE: 'value_type::ellipse', + RECTANGLE: 'value_type::rectangle', MULTIPOINT: 'value_type::multipoint', CIRCLE: 'value_type::circle', + ROI_THRESHOLD: 'value_type::roiThreshold', + ROI_THRESHOLD_MANUAL: 'value_type::roiThresholdManual', }; +/** + * MeasurementService class that supports source management and measurement management. + * Sources can be any library that can provide "annotations" (e.g. cornerstone-tools, cornerstone, etc.) + * The flow, is that by creating a source and mappings (annotation <-> measurement), we + * can convert back and forth between the two. MeasurementPanel in OHIF uses the measurement service + * to manage the measurements, and any edit to the measurements will be reflected back at the + * library level state (e.g. cornerstone-tools, cornerstone, etc.) by converting the + * edited measurements back to the original annotations and then updating the annotations. + * + * Note and Todo: We should be able to support measurements that are composed of multiple + * annotations, but that is not the case at the moment. + */ class MeasurementService { constructor() { this.sources = {}; @@ -92,6 +114,45 @@ class MeasurementService { Object.assign(this, pubSubServiceInterface); } + /** + * Adds the given schema to the measurement service schema list. + * This method should be used to add custom tool schema to the measurement service. + * @param {Array} schema schema for validation + */ + addMeasurementSchemaKeys(schema) { + if (!Array.isArray(schema)) { + schema = [schema]; + } + + MEASUREMENT_SCHEMA_KEYS.push(...schema); + } + + /** + * Adds the given valueType to the measurement service valueType object. + * This method should be used to add custom valueType to the measurement service. + * @param {*} valueType + * @returns + */ + addValueType(valueType) { + if (VALUE_TYPES[valueType]) { + return; + } + + // check if valuetype is valid , and if values are strings + if (!valueType || typeof valueType !== 'object') { + console.warn( + `MeasurementService: addValueType: invalid valueType: ${valueType}` + ); + return; + } + + Object.keys(valueType).forEach(key => { + if (!VALUE_TYPES[key]) { + VALUE_TYPES[key] = valueType[key]; + } + }); + } + /** * Get all measurements. * @@ -106,17 +167,17 @@ class MeasurementService { } /** - * Get specific measurement by its id. + * Get specific measurement by its uid. * - * @param {string} id Id of the measurement + * @param {string} uid measurement uid * @return {Measurement} Measurement instance */ - getMeasurement(id) { + getMeasurement(measurementUID) { let measurement = null; - const measurements = this.measurements[id]; + const measurements = this.measurements[measurementUID]; if (measurements && Object.keys(measurements).length > 0) { - measurement = this.measurements[id]; + measurement = this.measurements[measurementUID]; } return measurement; @@ -138,69 +199,71 @@ class MeasurementService { throw new Error('Source version not provided.'); } - const id = guid(); + // Go over all the keys inside the sources and check if the source + // name and version matches with the existing sources. + const sourceKeys = Object.keys(this.sources); + + for (let i = 0; i < sourceKeys.length; i++) { + const source = this.sources[sourceKeys[i]]; + if (source.name === name && source.version === version) { + return source; + } + } + + const uid = guid(); const source = { - id, + uid, name, version, }; - source.addOrUpdate = (definition, measurement) => { - return this.addOrUpdate(source, definition, measurement); + + source.annotationToMeasurement = (annotationType, annotation) => { + return this.annotationToMeasurement(source, annotationType, annotation); }; - source.remove = id => { - return this.remove(id, source); + + source.remove = (measurementUID, eventDetails) => { + return this.remove(measurementUID, source, eventDetails); }; - source.getAnnotation = (definition, measurementId) => { - return this.getAnnotation(source, definition, measurementId); + + source.getAnnotation = (annotationType, measurementId) => { + return this.getAnnotation(source, annotationType, measurementId); }; log.info(`New '${name}@${version}' source added.`); - this.sources[id] = source; + this.sources[uid] = source; return source; } getSource(name, version) { const { sources } = this; - const id = this._getSourceId(name, version); + const uid = this._getSourceUID(name, version); - return sources[id]; + return sources[uid]; } getSourceMappings(name, version) { const { mappings } = this; - const id = this._getSourceId(name, version); - - return mappings[id]; - } - - _getSourceId(name, version) { - const { sources } = this; - - const sourceId = Object.keys(sources).find(sourceId => { - const source = sources[sourceId]; - - return source.name === name && source.version === version; - }); + const uid = this._getSourceUID(name, version); - return sourceId; + return mappings[uid]; } /** * Add a new measurement matching criteria along with mapping functions. * * @param {MeasurementSource} source Measurement source instance - * @param {string} definition Definition of the measurement (Annotation Type) + * @param {string} annotationType annotation type to match which can be e.g., Length, Bidirectional, etc. * @param {MatchingCriteria} matchingCriteria The matching criteria - * @param {Function} toSourceSchema Mapping function to source schema + * @param {Function} toAnnotationSchema Mapping function to annotation schema * @param {Function} toMeasurementSchema Mapping function to measurement schema * @return void */ addMapping( source, - definition, + annotationType, matchingCriteria, - toSourceSchema, + toAnnotationSchema, toMeasurementSchema ) { if (!this._isValidSource(source)) { @@ -211,11 +274,11 @@ class MeasurementService { throw new Error('Matching criteria not provided.'); } - if (!definition) { - throw new Error('Definition not provided.'); + if (!annotationType) { + throw new Error('annotationType not provided.'); } - if (!toSourceSchema) { + if (!toAnnotationSchema) { throw new Error('Mapping function to source schema not provided.'); } @@ -225,19 +288,19 @@ class MeasurementService { const mapping = { matchingCriteria, - definition, - toSourceSchema, + annotationType, + toAnnotationSchema, toMeasurementSchema, }; - if (Array.isArray(this.mappings[source.id])) { - this.mappings[source.id].push(mapping); + if (Array.isArray(this.mappings[source.uid])) { + this.mappings[source.uid].push(mapping); } else { - this.mappings[source.id] = [mapping]; + this.mappings[source.uid] = [mapping]; } log.info( - `New measurement mapping added to source '${this._getSourceInfo( + `New measurement mapping added to source '${this._getSourceToString( source )}'.` ); @@ -247,81 +310,82 @@ class MeasurementService { * Get annotation for specific source. * * @param {MeasurementSource} source Measurement source instance - * @param {string} definition The source definition - * @param {string} measurementId The measurement service measurement id + * @param {string} annotationType The source annotationType + * @param {string} measurementUID The measurement service measurement uid * @return {Object} Source measurement schema */ - getAnnotation(source, definition, measurementId) { + getAnnotation(source, annotationType, measurementUID) { if (!this._isValidSource(source)) { log.warn('Invalid source. Exiting early.'); return; } - if (!definition) { - log.warn('No source definition provided. Exiting early.'); + if (!annotationType) { + log.warn('No source annotationType provided. Exiting early.'); return; } + const measurement = this.getMeasurement(measurementUID); const mapping = this._getMappingByMeasurementSource( - measurementId, - definition + measurement, + annotationType ); - const measurement = this.getMeasurement(measurementId); - if (mapping) return mapping.toSourceSchema(measurement, definition); + + if (mapping) { + return mapping.toAnnotationSchema(measurement, annotationType); + } const matchingMapping = this._getMatchingMapping( source, - definition, + annotationType, measurement ); if (matchingMapping) { log.info('Matching mapping found:', matchingMapping); - const { toSourceSchema, definition } = matchingMapping; - return toSourceSchema(measurement, definition); + const { toAnnotationSchema, annotationType } = matchingMapping; + return toAnnotationSchema(measurement, annotationType); } } - update(id, measurement, notYetUpdatedAtSource = false) { - if (this.measurements[id]) { - const updatedMeasurement = { - ...measurement, - modifiedTimestamp: Math.floor(Date.now() / 1000), - }; + update(measurementUID, measurement, notYetUpdatedAtSource = false) { + if (!this.measurements[measurementUID]) { + return; + } - log.info( - `Updating internal measurement representation...`, - updatedMeasurement - ); + const updatedMeasurement = { + ...measurement, + modifiedTimestamp: Math.floor(Date.now() / 1000), + }; - this.measurements[id] = updatedMeasurement; + log.info( + `Updating internal measurement representation...`, + updatedMeasurement + ); - this._broadcastEvent( - // Add an internal flag to say the measurement has not yet been updated at source. - this.EVENTS.MEASUREMENT_UPDATED, - { - source: measurement.source, - measurement: updatedMeasurement, - notYetUpdatedAtSource, - } - ); + this.measurements[measurementUID] = updatedMeasurement; - return updatedMeasurement.id; - } + this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, { + source: measurement.source, + measurement: updatedMeasurement, + notYetUpdatedAtSource, + }); + + return updatedMeasurement.uid; } /** * Add a raw measurement into a source so that it may be * Converted to/from annotation in the same way. E.g. import serialized data - * Of the same form as the measurement source. + * of the same form as the measurement source. * @param {MeasurementSource} source The measurement source instance. - * @param {string} definition The source definition you want to add the measurement to. + * @param {string} annotationType The source annotationType you want to add the measurement to. * @param {object} data The data you wish to add to the source. - * @param {function} toMeasurementSchema A function to get the `data` into the same shape as the source definition. + * @param {function} toMeasurementSchema A function to get the `data` into the same shape as the source annotationType. */ addRawMeasurement( source, - definition, + annotationType, data, toMeasurementSchema, dataSource = {} @@ -331,10 +395,10 @@ class MeasurementService { return; } - const sourceInfo = this._getSourceInfo(source); + const sourceInfo = this._getSourceToString(source); - if (!definition) { - log.warn('No source definition provided. Exiting early.'); + if (!annotationType) { + log.warn('No source annotationType provided. Exiting early.'); return; } @@ -347,13 +411,11 @@ class MeasurementService { let measurement = {}; try { - /* Convert measurement */ measurement = toMeasurementSchema(data); - /* Assign measurement source instance */ measurement.source = source; } catch (error) { log.warn( - `Failed to map '${sourceInfo}' measurement for definition ${definition}:`, + `Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}:`, error.message ); return; @@ -366,31 +428,27 @@ class MeasurementService { return; } - let internalId = data.id; - if (!internalId) { - internalId = guid(); - log.warn(`Measurement ID not found. Generating UID: ${internalId}`); + let internalUID = data.id; + if (!internalUID) { + internalUID = guid(); + log.warn(`Measurement ID not found. Generating UID: ${internalUID}`); } const newMeasurement = { ...measurement, modifiedTimestamp: Math.floor(Date.now() / 1000), - id: internalId, + uid: internalUID, }; - if (this.measurements[internalId]) { - log.info( - `Measurement already defined. Updating measurement.`, - newMeasurement - ); - this.measurements[internalId] = newMeasurement; + if (this.measurements[internalUID]) { + this.measurements[internalUID] = newMeasurement; this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, { source, measurement: newMeasurement, }); } else { log.info(`Measurement added.`, newMeasurement); - this.measurements[internalId] = newMeasurement; + this.measurements[internalUID] = newMeasurement; this._broadcastEvent(this.EVENTS.RAW_MEASUREMENT_ADDED, { source, measurement: newMeasurement, @@ -406,20 +464,20 @@ class MeasurementService { * Adds or update persisted measurements. * * @param {MeasurementSource} source The measurement source instance - * @param {string} definition The source definition - * @param {Measurement} measurement The source measurement - * @return {string} A measurement id + * @param {string} annotationType The source annotationType + * @param {EventDetail} sourceAnnotationDetail for the annotation event + * @return {string} A measurement uid */ - addOrUpdate(source, definition, sourceMeasurement) { + annotationToMeasurement(source, annotationType, sourceAnnotationDetail) { if (!this._isValidSource(source)) { throw new Error('Invalid source.'); } - if (!definition) { - throw new Error('No source definition provided.'); + if (!annotationType) { + throw new Error('No source annotationType provided.'); } - const sourceInfo = this._getSourceInfo(source); + const sourceInfo = this._getSourceToString(source); if (!this._sourceHasMappings(source)) { throw new Error( @@ -429,19 +487,17 @@ class MeasurementService { let measurement = {}; try { - const sourceMappings = this.mappings[source.id]; + const sourceMappings = this.mappings[source.uid]; const { toMeasurementSchema } = sourceMappings.find( - mapping => mapping.definition === definition + mapping => mapping.annotationType === annotationType ); /* Convert measurement */ - measurement = toMeasurementSchema(sourceMeasurement); - - /* Assign measurement source instance */ + measurement = toMeasurementSchema(sourceAnnotationDetail); measurement.source = source; } catch (error) { throw new Error( - `Failed to map '${sourceInfo}' measurement for definition ${definition}:`, + `Failed to map '${sourceInfo}' measurement for annotationType ${annotationType}:`, error.message ); } @@ -452,24 +508,23 @@ class MeasurementService { ); } - let internalId = sourceMeasurement.id; - if (!internalId) { - internalId = guid(); - log.info(`Measurement ID not found. Generating UID: ${internalId}`); + // Todo: we are using uid on the eventDetail, it should be uid of annotation + let internalUID = sourceAnnotationDetail.uid; + if (!internalUID) { + internalUID = guid(); + log.info( + `Annotation does not have UID, Generating UID for the created Measurement: ${internalUID}` + ); } const newMeasurement = { ...measurement, modifiedTimestamp: Math.floor(Date.now() / 1000), - id: internalId, + uid: internalUID, }; - if (this.measurements[internalId]) { - log.info( - `Measurement already defined. Updating measurement.`, - newMeasurement - ); - this.measurements[internalId] = newMeasurement; + if (this.measurements[internalUID]) { + this.measurements[internalUID] = newMeasurement; this._broadcastEvent(this.EVENTS.MEASUREMENT_UPDATED, { source, measurement: newMeasurement, @@ -477,51 +532,52 @@ class MeasurementService { }); } else { log.info('Measurement added.', newMeasurement); - this.measurements[internalId] = newMeasurement; + this.measurements[internalUID] = newMeasurement; this._broadcastEvent(this.EVENTS.MEASUREMENT_ADDED, { source, measurement: newMeasurement, }); } - return newMeasurement.id; + return newMeasurement.uid; } /** * Removes a measurement and broadcasts the removed event. * - * @param {string} id The measurement id + * @param {string} measurementUID The measurement uid * @param {MeasurementSource} source The measurement source instance - * @return {string} The removed measurement id */ - remove(id, source) { - if (!id || !this.measurements[id]) { - log.warn(`No id provided, or unable to find measurement by id.`); + remove(measurementUID, source, eventDetails) { + if (!measurementUID || !this.measurements[measurementUID]) { + log.warn(`No uid provided, or unable to find measurement by uid.`); return; } - delete this.measurements[id]; + delete this.measurements[measurementUID]; this._broadcastEvent(this.EVENTS.MEASUREMENT_REMOVED, { source, - measurement: id, // This is weird :shrug: + measurement: measurementUID, + ...eventDetails, }); } clearMeasurements() { + // Make a copy of the measurements + const measurements = { ...this.measurements }; this.measurements = {}; this._jumpToMeasurementCache = {}; - this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED); + this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED, { measurements }); } - jumpToMeasurement(viewportIndex, id) { - const measurement = this.measurements[id]; + jumpToMeasurement(viewportIndex, measurementUID) { + const measurement = this.measurements[measurementUID]; if (!measurement) { - log.warn(`No id provided, or unable to find measurement by id.`); + log.warn(`No measurement uid, or unable to find by uid.`); return; } - - this._addJumpToMeasurement(viewportIndex, id); + this._addJumpToMeasurement(viewportIndex, measurementUID); const eventName = this.EVENTS.JUMP_TO_MEASUREMENT; @@ -535,10 +591,6 @@ class MeasurementService { } } - _addJumpToMeasurement(viewportIndex, id) { - this._jumpToMeasurementCache[viewportIndex] = id; - } - getJumpToMeasurement(viewportIndex) { return this._jumpToMeasurementCache[viewportIndex]; } @@ -547,36 +599,43 @@ class MeasurementService { delete this._jumpToMeasurementCache[viewportIndex]; } - _getMappingByMeasurementSource(measurementId, definition) { - const measurement = this.getMeasurement(measurementId); + _getSourceUID(name, version) { + const { sources } = this; + + const sourceUID = Object.keys(sources).find(sourceUID => { + const source = sources[sourceUID]; + + return source.name === name && source.version === version; + }); + + return sourceUID; + } + + _addJumpToMeasurement(viewportIndex, measurementUID) { + this._jumpToMeasurementCache[viewportIndex] = measurementUID; + } + + _getMappingByMeasurementSource(measurement, annotationType) { if (this._isValidSource(measurement.source)) { - return this.mappings[measurement.source.id].find( - m => m.definition === definition + return this.mappings[measurement.source.uid].find( + m => m.annotationType === annotationType ); } } - /** - * Clear all measurements and broadcasts cleared event. - */ - clear() { - this.measurements = {}; - this._broadcastEvent(this.EVENTS.MEASUREMENTS_CLEARED); - } - /** * Get measurement mapping function if matching criteria. * * @param {MeasurementSource} source Measurement source instance - * @param {string} definition The source definition + * @param {string} annotationType The source annotationType * @param {Measurement} measurement The measurement service measurement * @return {Object} The mapping based on matched criteria */ - _getMatchingMapping(source, definition, measurement) { - const sourceMappings = this.mappings[source.id]; + _getMatchingMapping(source, annotationType, measurement) { + const sourceMappings = this.mappings[source.uid]; const sourceMappingsByDefinition = sourceMappings.filter( - mapping => mapping.definition === definition + mapping => mapping.annotationType === annotationType ); /* Criteria Matching */ @@ -594,7 +653,7 @@ class MeasurementService { * @param {MeasurementSource} source Measurement source * @return {string} Source information */ - _getSourceInfo(source) { + _getSourceToString(source) { return `${source.name}@${source.version}`; } @@ -605,7 +664,7 @@ class MeasurementService { * @return {boolean} Measurement source validation */ _isValidSource(source) { - return source && this.sources[source.id]; + return source && this.sources[source.uid]; } /** @@ -616,7 +675,8 @@ class MeasurementService { */ _sourceHasMappings(source) { return ( - Array.isArray(this.mappings[source.id]) && this.mappings[source.id].length + Array.isArray(this.mappings[source.uid]) && + this.mappings[source.uid].length ); } diff --git a/platform/core/src/services/MeasurementService/MeasurementService.test.js b/platform/core/src/services/MeasurementService/MeasurementService.test.js index 14752ba1d49..6d6fa6eb84f 100644 --- a/platform/core/src/services/MeasurementService/MeasurementService.test.js +++ b/platform/core/src/services/MeasurementService/MeasurementService.test.js @@ -11,7 +11,7 @@ describe('MeasurementService.js', () => { let measurementService; let measurement; let source; - let definition; + let annotationType; let matchingCriteria; let toSourceSchema; let toMeasurement; @@ -20,9 +20,9 @@ describe('MeasurementService.js', () => { beforeEach(() => { measurementService = new MeasurementService(); source = measurementService.createSource('Test', '1'); - definition = 'Length'; + annotationType = 'Length'; annotation = { - toolName: definition, + toolName: annotationType, measurementData: {}, }; measurement = { @@ -47,7 +47,7 @@ describe('MeasurementService.js', () => { } return measurement; - } + }; matchingCriteria = { valueType: measurementService.VALUE_TYPES.POLYLINE, points: 2, @@ -63,13 +63,13 @@ describe('MeasurementService.js', () => { it('throws Error if no name provided', () => { expect(() => { - measurementService.createSource(null, '1') + measurementService.createSource(null, '1'); }).toThrow(new Error('Source name not provided.')); }); it('throws Error if no version provided', () => { expect(() => { - measurementService.createSource('Testing', null) + measurementService.createSource('Testing', null); }).toThrow(new Error('Source version not provided.')); }); }); @@ -78,7 +78,7 @@ describe('MeasurementService.js', () => { it('adds new mapping', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement @@ -91,7 +91,7 @@ describe('MeasurementService.js', () => { measurementService.addMapping( invalidSource, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement @@ -103,7 +103,7 @@ describe('MeasurementService.js', () => { expect(() => { measurementService.addMapping( source, - definition, + annotationType, null, toSourceSchema, toMeasurement @@ -111,12 +111,11 @@ describe('MeasurementService.js', () => { }).toThrow(new Error('Matching criteria not provided.')); }); - it('throws Error if no source provided', () => { expect(() => { measurementService.addMapping( null /* source */, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement @@ -124,23 +123,23 @@ describe('MeasurementService.js', () => { }).toThrow(new Error('Invalid source.')); }); - it('logs warning and return early if no definition provided', () => { + it('logs warning and return early if no AnnotationType provided', () => { expect(() => { measurementService.addMapping( source, - null /* definition */, + null /* AnnotationType */, matchingCriteria, toSourceSchema, toMeasurement ); - }).toThrow(new Error('Definition not provided.')); + }).toThrow(new Error('annotationType not provided.')); }); it('throws Error if no measurement mapping function provided', () => { expect(() => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, null /* toSourceSchema */, toMeasurement @@ -152,7 +151,7 @@ describe('MeasurementService.js', () => { expect(() => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, null /* toMeasurement */ @@ -165,27 +164,39 @@ describe('MeasurementService.js', () => { it('get annotation based on matched criteria', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement ); - const measurementId = source.addOrUpdate(definition, annotation); - const mappedAnnotation = source.getAnnotation(definition, measurementId); + const measurementId = source.annotationToMeasurement( + annotationType, + annotation + ); + const mappedAnnotation = source.getAnnotation( + annotationType, + measurementId + ); expect(annotation).toBe(mappedAnnotation); }); - it('get annotation based on source and definition', () => { + it('get annotation based on source and annotationType', () => { measurementService.addMapping( source, - definition, + annotationType, {}, toSourceSchema, toMeasurement ); - const measurementId = source.addOrUpdate(definition, annotation); - const mappedAnnotation = source.getAnnotation(definition, measurementId); + const measurementId = source.annotationToMeasurement( + annotationType, + annotation + ); + const mappedAnnotation = source.getAnnotation( + annotationType, + measurementId + ); expect(annotation).toBe(mappedAnnotation); }); @@ -201,14 +212,14 @@ describe('MeasurementService.js', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement ); - source.addOrUpdate(definition, measurement); - source.addOrUpdate(definition, anotherMeasurement); + source.annotationToMeasurement(annotationType, measurement); + source.annotationToMeasurement(annotationType, anotherMeasurement); const measurements = measurementService.getMeasurements(); @@ -220,34 +231,34 @@ describe('MeasurementService.js', () => { it('return measurement service measurement with given id', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement ); - const id = source.addOrUpdate(definition, measurement); - const returnedMeasurement = measurementService.getMeasurement(id); + const uid = source.annotationToMeasurement(annotationType, measurement); + const returnedMeasurement = measurementService.getMeasurement(uid); /* Clear dynamic data */ delete returnedMeasurement.modifiedTimestamp; - expect({ id, ...measurement }).toEqual(returnedMeasurement); + expect({ uid, ...measurement }).toEqual(returnedMeasurement); }); }); - describe('addOrUpdate()', () => { + describe('annotationToMeasurement()', () => { it('adds new measurements', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement ); - source.addOrUpdate(definition, measurement); - source.addOrUpdate(definition, measurement); + source.annotationToMeasurement(annotationType, measurement); + source.annotationToMeasurement(annotationType, measurement); const measurements = measurementService.getMeasurements(); @@ -256,39 +267,39 @@ describe('MeasurementService.js', () => { it('fails to add new measurements when no mapping', () => { expect(() => { - source.addOrUpdate(definition, measurement); - }).toThrow() + source.annotationToMeasurement(annotationType, measurement); + }).toThrow(); }); it('fails to add new measurements when invalid mapping function', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, 1 /* Invalid */ ); expect(() => { - source.addOrUpdate(definition, measurement); - }).toThrow() + source.annotationToMeasurement(annotationType, measurement); + }).toThrow(); }); - it('adds new measurement with custom id', () => { - const newMeasurement = { id: 1, ...measurement }; + it('adds new measurement with custom uid', () => { + const newMeasurement = { uid: 1, ...measurement }; measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement ); /* Add new measurement */ - source.addOrUpdate(definition, newMeasurement); + source.annotationToMeasurement(annotationType, newMeasurement); const savedMeasurement = measurementService.getMeasurement( - newMeasurement.id + newMeasurement.uid ); /* Clear dynamic data */ @@ -303,32 +314,32 @@ describe('MeasurementService.js', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement ); expect(() => { - source.addOrUpdate(definition, measurement); - }).toThrow() + source.annotationToMeasurement(annotationType, measurement); + }).toThrow(); }); it('updates existing measurement', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement ); - const id = source.addOrUpdate(definition, measurement); + const uid = source.annotationToMeasurement(annotationType, measurement); measurement.unit = 'HU'; - source.addOrUpdate(definition, { id, ...measurement }); - const updatedMeasurement = measurementService.getMeasurement(id); + source.annotationToMeasurement(annotationType, { uid, ...measurement }); + const updatedMeasurement = measurementService.getMeasurement(uid); expect(updatedMeasurement.unit).toBe('HU'); }); @@ -338,7 +349,7 @@ describe('MeasurementService.js', () => { it('subscribers receive broadcasted add event', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement @@ -354,7 +365,7 @@ describe('MeasurementService.js', () => { ); /* Add new measurement */ - source.addOrUpdate(definition, measurement); + source.annotationToMeasurement(annotationType, measurement); expect(addCallbackWasCalled).toBe(true); }); @@ -362,7 +373,7 @@ describe('MeasurementService.js', () => { it('subscribers receive broadcasted update event', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement @@ -378,10 +389,10 @@ describe('MeasurementService.js', () => { ); /* Create measurement */ - const id = source.addOrUpdate(definition, measurement); + const uid = source.annotationToMeasurement(annotationType, measurement); /* Update measurement */ - source.addOrUpdate(definition, { id, ...measurement }); + source.annotationToMeasurement(annotationType, { uid, ...measurement }); expect(updateCallbackWasCalled).toBe(true); }); @@ -389,7 +400,7 @@ describe('MeasurementService.js', () => { it('unsubscribes a listener', () => { measurementService.addMapping( source, - definition, + annotationType, matchingCriteria, toSourceSchema, toMeasurement @@ -408,7 +419,7 @@ describe('MeasurementService.js', () => { unsubscribe(); /* Create measurement */ - source.addOrUpdate(definition, measurement); + source.annotationToMeasurement(annotationType, measurement); expect(updateCallbackWasCalled).toBe(false); }); diff --git a/platform/core/src/services/SegmentationService/SegmentationService.js b/platform/core/src/services/SegmentationService/SegmentationService.js new file mode 100644 index 00000000000..b0daa6fc073 --- /dev/null +++ b/platform/core/src/services/SegmentationService/SegmentationService.js @@ -0,0 +1,136 @@ +import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; + +const EVENTS = { + SEGMENTATION_UPDATED: 'event::segmentation_updated', + SEGMENTATION_ADDED: 'event::segmentation_added', + SEGMENTATION_REMOVED: 'event::segmentation_removed', + SEGMENTATION_VISIBILITY_CHANGED: 'event::SEGMENTATION_VISIBILITY_CHANGED', +}; + +const VALUE_TYPES = {}; + +class SegmentationService { + constructor() { + this.segmentations = {}; + this.listeners = {}; + Object.defineProperty(this, 'EVENTS', { + value: EVENTS, + writable: false, + enumerable: true, + configurable: false, + }); + + Object.assign(this, pubSubServiceInterface); + } + + /** + * Get all segmentations. + * + * @return Array of segmentations + */ + getSegmentations() { + const segmentations = this._arrayOfObjects(this.segmentations); + return ( + segmentations && + segmentations.map(m => this.segmentations[Object.keys(m)[0]]) + ); + } + + /** + * Get specific segmentation by its id. + * + * @param id If of the segmentation + * @return segmentation instance + */ + getSegmentation(id) { + return this.segmentations[id]; + } + + addOrUpdateSegmentation( + id, + segmentationSchema, + notYetUpdatedAtSource = false + ) { + const segmentation = this.segmentations[id]; + + if (segmentation) { + Object.assign(segmentation, segmentationSchema); + + this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, { + id, + segmentation, + notYetUpdatedAtSource: notYetUpdatedAtSource, + }); + + return; + } + + this.segmentations[id] = { + ...segmentationSchema, + visible: true, + }; + + this._broadcastEvent(this.EVENTS.SEGMENTATION_ADDED, { + id, + segmentation, + }); + } + + /** + * Toggles the visibility of a segmentation in the state, and broadcasts the event. + * Note: this method does not update the segmentation state in the source. It only + * updates the state, and there should be separate listeners for that. + * @param ids segmentation ids + */ + toggleSegmentationsVisibility(ids) { + ids.forEach(id => { + const segmentation = this.segmentations[id]; + if (!segmentation) { + throw new Error(`Segmentation with id ${id} not found.`); + } + segmentation.visible = !segmentation.visible; + this._broadcastEvent(this.EVENTS.SEGMENTATION_VISIBILITY_CHANGED, { + segmentation, + }); + }); + } + + /** + * Removes a segmentation and broadcasts the removed event. + * + * @param {string} id The segmentation id + */ + remove(id) { + if (!id || !this.segmentations[id]) { + console.warn(`No id provided, or unable to find segmentation by id.`); + return; + } + delete this.segmentations[id]; + this._broadcastEvent(this.EVENTS.SEGMENTATION_REMOVED, { + id, + }); + } + + /** + * Clear all segmentations and broadcasts cleared event. + */ + clear() { + Object.keys(this.segmentations).forEach(id => { + this.remove(id); + }); + + this.segmentations = {}; + } + + /** + * Converts object of objects to array. + * + * @return {Array} Array of objects + */ + _arrayOfObjects = obj => { + return Object.entries(obj).map(e => ({ [e[0]]: e[1] })); + }; +} + +export default SegmentationService; +export { EVENTS, VALUE_TYPES }; diff --git a/platform/core/src/services/SegmentationService/index.js b/platform/core/src/services/SegmentationService/index.js new file mode 100644 index 00000000000..8cda4de8488 --- /dev/null +++ b/platform/core/src/services/SegmentationService/index.js @@ -0,0 +1,8 @@ +import SegmentationService from './SegmentationService'; + +export default { + name: 'SegmentationService', + create: ({ configuration = {} }) => { + return new SegmentationService(); + }, +}; diff --git a/platform/core/src/services/ToolBarService/ToolBarService.js b/platform/core/src/services/ToolBarService/ToolBarService.js index e14045e21bb..f9e5b951b93 100644 --- a/platform/core/src/services/ToolBarService/ToolBarService.js +++ b/platform/core/src/services/ToolBarService/ToolBarService.js @@ -24,7 +24,7 @@ export default class ToolBarService { // TODO: Do we need to track per context? Or do we allow for a mixed // definition that adapts based on context? this.state = { - primaryToolId: 'Wwwc', + primaryToolId: 'WindowLevel', toggles: { /* id: true/false */ }, @@ -43,11 +43,13 @@ export default class ToolBarService { reset() { this.unsubscriptions.forEach(unsub => unsub()); this.state = { - primaryToolId: 'Wwwc', + primaryToolId: 'WindowLevel', toggles: {}, groups: {}, }; this.unsubscriptions = []; + this.buttonSections = {}; + this.buttons = {}; } /** @@ -56,17 +58,24 @@ export default class ToolBarService { */ recordInteraction(interaction) { const commandsManager = this._commandsManager; - const { groupId, itemId, interactionType, commandName, commandOptions } = interaction; + const { groupId, itemId, interactionType, commands } = interaction; switch (interactionType) { case 'action': { + commands.forEach(({ commandName, commandOptions, context }) => { + if (commandName) { + commandsManager.runCommand(commandName, commandOptions, context); + } + }); break; } case 'tool': { this.state.primaryToolId = itemId; - // TODO: Force run this for all contexts? Even inactive? - // or... They'll just detect primaryToolId when they spin up and apply... - commandsManager.runCommand('setToolActive', commandOptions); + commands.forEach( + ({ commandName = 'setToolActive', commandOptions, context }) => { + commandsManager.runCommand(commandName, commandOptions, context); + } + ); break; } case 'toggle': { @@ -74,30 +83,41 @@ export default class ToolBarService { this.state.toggles[itemId] === undefined ? true : !this.state.toggles[itemId]; - if (commandOptions) { - commandOptions.toggledState = this.state.toggles[itemId]; - } + + const { commands } = interaction; + + commands.forEach(({ commandName, commandOptions, context }) => { + if (!commandOptions) { + commandOptions = {}; + } + + if (commandName) { + commandOptions.toggledState = this.state.toggles[itemId]; + commandsManager.runCommand(commandName, commandOptions, context); + } + }); break; } default: throw new Error(`Invalid interaction type: ${interactionType}`); } + // Todo: comment out for now // Run command if there's one associated // // NOTE: Should probably just do this for tools as well? // But would be nice if we could enforce at least the command name? - let unsubscribe; - if (commandName) { - unsubscribe = commandsManager.runCommand(commandName, commandOptions); - } - - // Storing the unsubscribe for later reseting - if (unsubscribe && typeof unsubscribe === 'function') { - if (this.unsubscriptions.indexOf(unsubscribe) === -1) { - this.unsubscriptions.push(unsubscribe); - } - } + // let unsubscribe; + // if (commandName) { + // unsubscribe = commandsManager.runCommand(commandName, commandOptions); + // } + + // // Storing the unsubscribe for later reseting + // if (unsubscribe && typeof unsubscribe === 'function') { + // if (this.unsubscriptions.indexOf(unsubscribe) === -1) { + // this.unsubscriptions.push(unsubscribe); + // } + // } // Track last touched id for each group if (groupId) { diff --git a/platform/core/src/services/ViewportGridService/ViewportGridService.js b/platform/core/src/services/ViewportGridService/ViewportGridService.js index 34a1a221168..c54e8dcbd35 100644 --- a/platform/core/src/services/ViewportGridService/ViewportGridService.js +++ b/platform/core/src/services/ViewportGridService/ViewportGridService.js @@ -4,7 +4,7 @@ const publicAPI = { name, getState: _getState, setActiveViewportIndex: _setActiveViewportIndex, - setDisplaysetForViewport: _setDisplaysetForViewport, + setDisplaySetsForViewport: _setDisplaySetsForViewport, setLayout: _setLayout, setCachedLayout: _setCachedLayout, setServiceImplementation, @@ -16,8 +16,8 @@ const serviceImplementation = { _getState: () => console.warn('getState() NOT IMPLEMENTED'), _setActiveViewportIndex: () => console.warn('setActiveViewportIndex() NOT IMPLEMENTED'), - _setDisplaysetForViewport: () => - console.warn('setDisplaysetForViewport() NOT IMPLEMENTED'), + _setDisplaySetsForViewport: () => + console.warn('setDisplaySetsForViewport() NOT IMPLEMENTED'), _setLayout: () => console.warn('setLayout() NOT IMPLEMENTED'), _reset: () => console.warn('reset() NOT IMPLEMENTED'), _setCachedLayout: () => console.warn('setCachedLayout() NOT IMPLEMENTED'), @@ -32,10 +32,10 @@ function _setActiveViewportIndex(index) { return serviceImplementation._setActiveViewportIndex(index); } -function _setDisplaysetForViewport({ viewportIndex, displaySetInstanceUID }) { - return serviceImplementation._setDisplaysetForViewport({ +function _setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUIDs }) { + return serviceImplementation._setDisplaySetsForViewport({ viewportIndex, - displaySetInstanceUID, + displaySetInstanceUIDs, }); } @@ -58,7 +58,7 @@ function _setCachedLayout({ numCols, numRows, viewports }) { function setServiceImplementation({ getState: getStateImplementation, setActiveViewportIndex: setActiveViewportIndexImplementation, - setDisplaysetForViewport: setDisplaysetForViewportImplementation, + setDisplaySetsForViewport: setDisplaySetsForViewportImplementation, setCachedLayout: setCachedLayoutImplementation, setLayout: setLayoutImplementation, reset: resetImplementation, @@ -70,8 +70,8 @@ function setServiceImplementation({ if (setActiveViewportIndexImplementation) { serviceImplementation._setActiveViewportIndex = setActiveViewportIndexImplementation; } - if (setDisplaysetForViewportImplementation) { - serviceImplementation._setDisplaysetForViewport = setDisplaysetForViewportImplementation; + if (setDisplaySetsForViewportImplementation) { + serviceImplementation._setDisplaySetsForViewport = setDisplaySetsForViewportImplementation; } if (setLayoutImplementation) { serviceImplementation._setLayout = setLayoutImplementation; diff --git a/platform/core/src/services/index.js b/platform/core/src/services/index.js index 87f714815c2..3ddd0834fc0 100644 --- a/platform/core/src/services/index.js +++ b/platform/core/src/services/index.js @@ -12,6 +12,7 @@ import CineService from './CineService'; import HangingProtocolService from './HangingProtocolService'; import pubSubServiceInterface from './_shared/pubSubServiceInterface'; import UserAuthenticationService from './UserAuthenticationService'; +import SegmentationService from './SegmentationService'; export { MeasurementService, @@ -28,4 +29,5 @@ export { CineService, pubSubServiceInterface, UserAuthenticationService, + SegmentationService, }; diff --git a/platform/core/src/utils/StackManager.js b/platform/core/src/utils/StackManager.js deleted file mode 100644 index 8a46c5d2b81..00000000000 --- a/platform/core/src/utils/StackManager.js +++ /dev/null @@ -1,136 +0,0 @@ -let stackMap = {}; -let configuration = {}; -const stackUpdatedCallbacks = []; - -/** - * Loop through the current series and add metadata to the - * Cornerstone meta data provider. This will be used to fill information - * into the viewport overlays, and to calculate reference lines and orientation markers - * @param {Object} stackMap stackMap object - * @param {Object} displaySet The set of images to make the stack from - * @return {Array} Array with image IDs - */ -function createAndAddStack( - stackMap, - displaySet, - dataSource, - stackUpdatedCallbacks -) { - const { - images, - displaySetInstanceUID, - StudyInstanceUID, - frameRate, - isClip, - initialImageIdIndex, - } = displaySet; - if (!images) { - return; - } - - const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); - - const stack = { - StudyInstanceUID, - displaySetInstanceUID, - imageIds, - frameRate, - isClip, - initialImageIdIndex, - }; - - stackMap[displaySetInstanceUID] = stack; - - return stack; -} - -configuration = { - createAndAddStack, -}; - -/** - * This object contains all the functions needed for interacting with the stack manager. - * Generally, findStack is the only function used. If you want to know when new stacks - * come in, you can register a callback with addStackUpdatedCallback. - */ -const StackManager = { - /** - * Removes all current stacks - */ - clearStacks() { - stackMap = {}; - }, - /** - * Create a stack from an image set, as well as add in the metadata on a per image bases. - * @param displaySet The set of images to make the stack from - * @return {Array} Array with image IDs - */ - makeAndAddStack(displaySet, dataSource) { - return configuration.createAndAddStack( - stackMap, - displaySet, - dataSource, - stackUpdatedCallbacks - ); - }, - /** - * Find a stack from the currently created stacks. - * @param displaySetInstanceUID The UID of the stack to find. - * @returns {*} undefined if not found, otherwise the stack object is returned. - */ - findStack(displaySetInstanceUID) { - return stackMap[displaySetInstanceUID]; - }, - /** - * Find a stack or create one if it has not been created yet - * @param displaySet The set of images to make the stack from - * @return {Array} Array with image IDs - */ - findOrCreateStack(displaySet, dataSource) { - let stack = this.findStack(displaySet.displaySetInstanceUID); - - if (!stack || !stack.imageIds) { - stack = this.makeAndAddStack(displaySet, dataSource); - } - - return stack; - }, - /** - * Gets the underlying map of displaySetInstanceUID to stack object. - * WARNING: Do not change this object. It directly affects the manager. - * @returns {{}} map of displaySetInstanceUID -> stack. - */ - getAllStacks() { - return stackMap; - }, - /** - * Adds in a callback to be called on a stack being added / updated. - * @param callback must accept at minimum one argument, - * which is the stack that was added / updated. - */ - addStackUpdatedCallback(callback) { - if (typeof callback !== 'function') { - throw new Error('callback must be provided as a function'); - } - stackUpdatedCallbacks.push(callback); - }, - /** - * Return configuration - */ - getConfiguration() { - return configuration; - }, - /** - * Set configuration, in order to provide compatibility - * with other systems by overriding this functions - * @param {Object} config object with functions to be overrided - * - * For now, only makeAndAddStack can be overrided - */ - setConfiguration(config) { - configuration = config; - }, -}; - -export { StackManager }; -export default StackManager; diff --git a/platform/core/src/utils/debounce.js b/platform/core/src/utils/debounce.js new file mode 100644 index 00000000000..83a16b0990a --- /dev/null +++ b/platform/core/src/utils/debounce.js @@ -0,0 +1,21 @@ +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. If `immediate` is passed, trigger the function on the +// leading edge, instead of the trailing. +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, + args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +} + +export default debounce; diff --git a/platform/core/src/utils/downloadCSVReport.js b/platform/core/src/utils/downloadCSVReport.js new file mode 100644 index 00000000000..12951a6a646 --- /dev/null +++ b/platform/core/src/utils/downloadCSVReport.js @@ -0,0 +1,115 @@ +import { DicomMetadataStore } from '../services/DicomMetadataStore/DicomMetadataStore'; + +export default function downloadCSVReport(measurementData) { + if (measurementData.length === 0) { + // Prevent download of report with no measurements. + return; + } + + const columns = [ + 'Patient ID', + 'Patient Name', + 'StudyInstanceUID', + 'SeriesInstanceUID', + 'SOPInstanceUID', + 'Label', + ]; + + const reportMap = {}; + measurementData.forEach(measurement => { + const { + referenceStudyUID, + referenceSeriesUID, + getReport, + uid, + } = measurement; + + if (!getReport) { + console.warn('Measurement does not have a getReport function'); + return; + } + + const seriesMetadata = DicomMetadataStore.getSeries( + referenceStudyUID, + referenceSeriesUID + ); + + const commonRowItems = _getCommonRowItems(measurement, seriesMetadata); + const report = getReport(measurement); + + reportMap[uid] = { + report, + commonRowItems, + }; + }); + + // get columns names inside the report from each measurement and + // add them to the rows array (this way we can add columns for any custom + // measurements that may be added in the future) + Object.keys(reportMap).forEach(id => { + const { report } = reportMap[id]; + report.columns.forEach(column => { + if (!columns.includes(column)) { + columns.push(column); + } + }); + }); + + const results = _mapReportsToRowArray(reportMap, columns); + + let csvContent = + 'data:text/csv;charset=utf-8,' + + results.map(res => res.join(',')).join('\n'); + + _createAndDownloadFile(csvContent); +} + +function _mapReportsToRowArray(reportMap, columns) { + const results = [columns]; + Object.keys(reportMap).forEach(id => { + const { report, commonRowItems } = reportMap[id]; + const row = []; + // For commonRowItems, find the correct index and add the value to the + // correct row in the results array + Object.keys(commonRowItems).forEach(key => { + const index = columns.indexOf(key); + const value = commonRowItems[key]; + row[index] = value; + }); + + // For each annotation data, find the correct index and add the value to the + // correct row in the results array + report.columns.forEach((column, index) => { + const colIndex = columns.indexOf(column); + const value = report.values[index]; + row[colIndex] = value; + }); + + results.push(row); + }); + + return results; +} + +function _getCommonRowItems(measurement, seriesMetadata) { + const firstInstance = seriesMetadata.instances[0]; + + return { + 'Patient ID': firstInstance.PatientID, // Patient ID + 'Patient Name': firstInstance.PatientName.Alphabetic, // PatientName + StudyInstanceUID: measurement.referenceStudyUID, // StudyInstanceUID + SeriesInstanceUID: measurement.referenceSeriesUID, // SeriesInstanceUID + SOPInstanceUID: measurement.SOPInstanceUID, // SOPInstanceUID + Label: measurement.label || '', // Label + }; +} + +function _createAndDownloadFile(csvContent) { + const encodedUri = encodeURI(csvContent); + + const link = document.createElement('a'); + link.setAttribute('href', encodedUri); + link.setAttribute('download', 'MeasurementReport.csv'); + document.body.appendChild(link); + link.click(); +} diff --git a/platform/core/src/utils/imageIdToURI.js b/platform/core/src/utils/imageIdToURI.js new file mode 100644 index 00000000000..7be55b07588 --- /dev/null +++ b/platform/core/src/utils/imageIdToURI.js @@ -0,0 +1,12 @@ +/** + * Removes the data loader scheme from the imageId + * + * @param {string} imageId Image ID + * @returns {string} imageId without the data loader scheme + * @memberof Cache + */ +export default function imageIdToURI(imageId) { + const colonIndex = imageId.indexOf(':'); + + return imageId.substring(colonIndex + 1); +} diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index 6cee230c54b..923b28c402c 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -1,11 +1,9 @@ import ObjectPath from './objectPath'; -import StackManager from './StackManager.js'; import absoluteUrl from './absoluteUrl'; import guid from './guid'; import sortBy from './sortBy.js'; import sortBySeriesDate from './sortBySeriesDate.js'; import writeScript from './writeScript.js'; -import DicomLoaderService from './dicomLoaderService.js'; import b64toBlob from './b64toBlob.js'; //import loadAndCacheDerivedDisplaySets from './loadAndCacheDerivedDisplaySets.js'; import urlUtil from './urlUtil'; @@ -22,9 +20,13 @@ import progressTrackingUtils from './progressTrackingUtils'; import isLowPriorityModality from './isLowPriorityModality'; import { isImage } from './isImage'; import isDisplaySetReconstructable from './isDisplaySetReconstructable'; +import imageIdToURI from './imageIdToURI'; +import debounce from './debounce'; +import roundNumber from './roundNumber'; +import downloadCSVReport from './downloadCSVReport'; // Commented out unused functionality. -// Need to implement new mechanism for dervived displaySets using the displaySetManager. +// Need to implement new mechanism for derived displaySets using the displaySetManager. const utils = { guid, @@ -36,9 +38,8 @@ const utils = { formatDate, formatPN, b64toBlob, - StackManager, - DicomLoaderService, urlUtil, + imageIdToURI, //loadAndCacheDerivedDisplaySets, makeDeferred, makeCancelable, @@ -51,6 +52,9 @@ const utils = { isLowPriorityModality, isImage, isDisplaySetReconstructable, + debounce, + roundNumber, + downloadCSVReport, }; export { @@ -61,8 +65,6 @@ export { formatDate, writeScript, b64toBlob, - StackManager, - DicomLoaderService, urlUtil, //loadAndCacheDerivedDisplaySets, makeDeferred, @@ -76,6 +78,10 @@ export { isLowPriorityModality, isImage, isDisplaySetReconstructable, + imageIdToURI, + debounce, + roundNumber, + downloadCSVReport, }; export default utils; diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index c75a84e64c0..d85a1d86cdf 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -10,14 +10,16 @@ describe('Top level exports', () => { 'sortBySeriesDate', 'isLowPriorityModality', 'writeScript', + 'debounce', + 'downloadCSVReport', + 'imageIdToURI', + 'roundNumber', 'b64toBlob', - 'StackManager', 'formatDate', 'formatPN', //'loadAndCacheDerivedDisplaySets', 'isDisplaySetReconstructable', 'isImage', - 'DicomLoaderService', 'urlUtil', 'makeDeferred', 'makeCancelable', diff --git a/platform/core/src/utils/isDisplaySetReconstructable.js b/platform/core/src/utils/isDisplaySetReconstructable.js index 0d0f250b11f..a7357848593 100644 --- a/platform/core/src/utils/isDisplaySetReconstructable.js +++ b/platform/core/src/utils/isDisplaySetReconstructable.js @@ -1,3 +1,9 @@ +import toNumber from './toNumber'; + +// TODO: Is 10% a reasonable spacingTolerance for spacing? +const spacingTolerance = 0.2; +const iopTolerance = 0.01; + /** * Checks if a series is reconstructable to a 3D volume. * @@ -8,7 +14,7 @@ export default function isDisplaySetReconstructable(instances) { return { value: false }; } - const firstInstance = instances[0].getData().metadata; + const firstInstance = instances[0]; const Modality = firstInstance.Modality; const isMultiframe = firstInstance.NumberOfFrames > 1; @@ -35,19 +41,21 @@ function processMultiframe(instance) { } function processSingleframe(instances) { - const firstImage = instances[0].getData().metadata; - const firstImageRows = firstImage.Rows; - const firstImageColumns = firstImage.Columns; - const firstImageSamplesPerPixel = firstImage.SamplesPerPixel; - const firstImageOrientationPatient = firstImage.ImageOrientationPatient; - const firstImagePositionPatient = firstImage.ImagePositionPatient; + const firstImage = instances[0]; + const firstImageRows = toNumber(firstImage.Rows); + const firstImageColumns = toNumber(firstImage.Columns); + const firstImageSamplesPerPixel = toNumber(firstImage.SamplesPerPixel); + const firstImageOrientationPatient = toNumber( + firstImage.ImageOrientationPatient + ); + const firstImagePositionPatient = toNumber(firstImage.ImagePositionPatient); // Can't reconstruct if we: // -- Have a different dimensions within a displaySet. // -- Have a different number of components within a displaySet. // -- Have different orientations within a displaySet. for (let i = 1; i < instances.length; i++) { - const instance = instances[i].getData().metadata; + const instance = instances[i]; const { Rows, Columns, @@ -55,11 +63,13 @@ function processSingleframe(instances) { ImageOrientationPatient, } = instance; + const imageOrientationPatient = toNumber(ImageOrientationPatient); + if ( Rows !== firstImageRows || Columns !== firstImageColumns || SamplesPerPixel !== firstImageSamplesPerPixel || - !_isSameOrientation(ImageOrientationPatient, firstImageOrientationPatient) + !_isSameOrientation(imageOrientationPatient, firstImageOrientationPatient) ) { return { value: false }; } @@ -71,8 +81,9 @@ function processSingleframe(instances) { // If spacing is on a uniform grid but we are missing frames, // Allow reconstruction, but pass back the number of missing frames. if (instances.length > 2) { - const lastIpp = instances[instances.length - 1].getData().metadata - .ImagePositionPatient; + const lastIpp = toNumber( + instances[instances.length - 1].ImagePositionPatient + ); // We can't reconstruct if we are missing ImagePositionPatient values if (!firstImagePositionPatient || !lastIpp) { @@ -86,11 +97,12 @@ function processSingleframe(instances) { let previousImagePositionPatient = firstImagePositionPatient; for (let i = 1; i < instances.length; i++) { - const instance = instances[i].getData().metadata; - const { ImagePositionPatient } = instance; + const instance = instances[i]; + // Todo: get metadata from OHIF.MetadataProvider + const imagePositionPatient = toNumber(instance.ImagePositionPatient); const spacingBetweenFrames = _getPerpendicularDistance( - ImagePositionPatient, + imagePositionPatient, previousImagePositionPatient ); const spacingIssue = _getSpacingIssue( @@ -108,7 +120,7 @@ function processSingleframe(instances) { } } - previousImagePositionPatient = ImagePositionPatient; + previousImagePositionPatient = imagePositionPatient; } } @@ -127,10 +139,6 @@ function _isSameOrientation(iop1, iop2) { ); } -// TODO: Is 10% a reasonable spacingTolerance for spacing? -const spacingTolerance = 0.1; -const iopTolerance = 0.01; - /** * Checks for spacing issues. * diff --git a/platform/core/src/utils/roundNumber.js b/platform/core/src/utils/roundNumber.js new file mode 100644 index 00000000000..b62e5df426e --- /dev/null +++ b/platform/core/src/utils/roundNumber.js @@ -0,0 +1,5 @@ +function _round(value, decimals) { + return Number(value.toFixed(decimals)); +} + +export default _round; diff --git a/platform/core/src/utils/toNumber.js b/platform/core/src/utils/toNumber.js new file mode 100644 index 00000000000..986a8d5181b --- /dev/null +++ b/platform/core/src/utils/toNumber.js @@ -0,0 +1,13 @@ +/** + * Returns the values as an array of javascript numbers + * + * @param val - The javascript object for the specified element in the metadata + * @returns {*} + */ +export default function toNumber(val) { + if (Array.isArray(val)) { + return val.map(v => (v !== undefined ? Number(v) : v)); + } else { + return val !== undefined ? Number(val) : val; + } +} diff --git a/platform/docs/docs/README.md b/platform/docs/docs/README.md index cd6cfe17bb5..b80491d078a 100644 --- a/platform/docs/docs/README.md +++ b/platform/docs/docs/README.md @@ -12,7 +12,7 @@ Key features: - Designed to load large radiology studies as quickly as possible. Retrieves metadata ahead of time and streams in imaging pixel data as needed. -- Leverages [Cornerstone.js](https://cornerstonejs.org/) for decoding, +- Leverages [Cornerstone3D](https://github.com/cornerstonejs/cornerstone3D-beta) for decoding, rendering, and annotating medical images. - Works out-of-the-box with Image Archives that support [DICOMWeb][dicom-web]. Offers a Data Source API for communicating with archives over proprietary API diff --git a/platform/docs/docs/development/architecture.md b/platform/docs/docs/development/architecture.md index 4bcebb0175e..a8fab99a5d8 100644 --- a/platform/docs/docs/development/architecture.md +++ b/platform/docs/docs/development/architecture.md @@ -24,15 +24,16 @@ you'll see the following: ```bash │ ├── extensions -│ ├── _example # Skeleton of example extension -│ ├── default # default functionalities -│ ├── cornerstone # 2D images w/ Cornerstone.js -│ ├── measurement-tracking # measurement tracking -│ ├── dicom-sr # Structured reports -│ └── dicom-pdf # View DICOM wrapped PDFs in viewport +│ ├── _example # Skeleton of example extension +│ ├── default # default functionalities +│ ├── cornerstone # 2D/3D images w/ Cornerstonejs +│ ├── cornerstone-dicom-sr # Structured reports +│ ├── measurement-tracking # measurement tracking +│ └── dicom-pdf # View DICOM wrapped PDFs in viewport │ ├── modes │ └── longitudinal # longitudinal measurement tracking mode +| └── basic-dev-mode # basic viewer with Cornerstone (a developer focused mode) │ ├── platform │ ├── core # Business Logic diff --git a/platform/docs/docs/development/continous-integration.md b/platform/docs/docs/development/continous-integration.md index 3c3124af83b..d6f036134ed 100644 --- a/platform/docs/docs/development/continous-integration.md +++ b/platform/docs/docs/development/continous-integration.md @@ -1,11 +1,11 @@ --- sidebar_position: 7 -sidebar_label: Continous Integration +sidebar_label: Continuos Integration --- -# Continous Integration (CI) +# Continuos Integration (CI) -This repository uses `CircleCI` and `Netlify` for continous integration. +This repository uses `CircleCI` and `Netlify` for Continuos integration. ## Deploy Previews diff --git a/platform/docs/docs/development/getting-started.md b/platform/docs/docs/development/getting-started.md index b9b3bc5d51a..c4e04151ceb 100644 --- a/platform/docs/docs/development/getting-started.md +++ b/platform/docs/docs/development/getting-started.md @@ -24,6 +24,11 @@ up-to-date with the upstream (original) repository. This is called a "Triangular Workflow" and is common for Open Source projects. The GitHub blog has a [good graphic that illustrates this setup][triangular-workflow]. +### `v3-stable` branch +Currently the stable branch for OHIF-v3 is `v3-stable`. Once the v3-stable branch has +feature parity with the master branch, `v3-stable` will be pushed to the master branch. +You can read more about the roadmap timeline [here](https://ohif.org/roadmap). + ### Private Alternatively, if you intend to use the OHIF Viewer as a starting point, and you @@ -106,6 +111,6 @@ yarn run build [add-remote-repo]: https://help.github.com/en/articles/fork-a-repo#step-3-configure-git-to-sync-your-fork-with-the-original-spoon-knife-repository [sync-changes]: https://help.github.com/en/articles/syncing-a-fork [triangular-workflow]: https://github.blog/2015-07-29-git-2-5-including-multiple-worktrees-and-triangular-workflows/#improved-support-for-triangular-workflows -[ohif-viewers-repo]: https://github.com/OHIF/Viewers +[ohif-viewers-repo]: https://github.com/OHIF/Viewers/tree/v3-stable [ohif-viewers]: https://github.com/OHIF/Viewers diff --git a/platform/docs/docs/platform/extensions/index.md b/platform/docs/docs/platform/extensions/index.md index b1920b52104..97a7e764741 100644 --- a/platform/docs/docs/platform/extensions/index.md +++ b/platform/docs/docs/platform/extensions/index.md @@ -65,6 +65,7 @@ export default { getContextModule() { /* */ }, getToolbarModule() { /* */ }, getHangingProtocolModule() { /* */ }, + getUtilityModule() { /* */ }, } ``` @@ -86,7 +87,7 @@ the top level [`extensions/`][ext-source] directory. - Default + default @@ -97,18 +98,18 @@ the top level [`extensions/`][ext-source] directory. - - Cornerstone + + cornerstone - Provides rendering functionalities for 2D images. + Provides 2d and 3d rendering functionalities - ViewportModule, CommandsModule + ViewportModule, CommandsModule, UtilityModule - DICOM PDF + dicom-pdf Renders PDFs for a specific SopClassUID. @@ -117,7 +118,16 @@ the top level [`extensions/`][ext-source] directory. - DICOM SR + dicom-video + + + Renders DICOM Video files. + + Viewport, SopClassHandler + + + + cornerstone-dicom-sr Maintained extensions for cornerstone and visualization of DICOM Structured Reports @@ -126,7 +136,7 @@ the top level [`extensions/`][ext-source] directory. - Measurement tracking + measurement-tracking Tracking measurements in the measurement panel @@ -290,6 +300,14 @@ differently. Adds hanging protocol rules + + + + Utility + + + Expose utility functions to the outside of extensions + diff --git a/platform/docs/docs/platform/extensions/lifecycle.md b/platform/docs/docs/platform/extensions/lifecycle.md index 223386bc633..ccb1392ee96 100644 --- a/platform/docs/docs/platform/extensions/lifecycle.md +++ b/platform/docs/docs/platform/extensions/lifecycle.md @@ -16,8 +16,8 @@ Extensions can implement specific lifecycle methods. ## preRegistration If an extension defines the `preRegistration` lifecycle hook, it is called -before any modules are registered in the `ExtensionManager`. This hook can be -used to: +before any modules are registered in the `ExtensionManager`. This hook is an +`async` function that can be used to perform: - initialize 3rd party libraries - register event listeners @@ -61,7 +61,7 @@ export default { * @param {CommandsManager} params.commandsManager * @returns void */ - preRegistration({ servicesManager, commandsManager, configuration }) { + async preRegistration({ servicesManager, commandsManager, configuration }) { console.log('Wiring up important stuff.'); window.importantStuff = () => { @@ -90,7 +90,7 @@ _Example `onModeEnter` hook implementation_ ```js export default { - id: '@ohif/extension-dicom-sr', + id: '@ohif/extension-cornerstone-dicom-sr', onModeEnter({ servicesManager }) { const { DisplaySetService } = servicesManager.services; diff --git a/platform/docs/docs/platform/extensions/modules/hpModule.md b/platform/docs/docs/platform/extensions/modules/hpModule.md index 649d6644eec..ce6880838e6 100644 --- a/platform/docs/docs/platform/extensions/modules/hpModule.md +++ b/platform/docs/docs/platform/extensions/modules/hpModule.md @@ -22,26 +22,40 @@ const deafultProtocol = { availableTo: {}, editableBy: {}, protocolMatchingRules: [], + toolGroupIds: ['default'], stages: [ { - id: 'nwzau7jDkEkL8djfr', - name: 'oneByOne', + id: 'hYbmMy3b7pz7GLiaT', + name: 'default', viewportStructure: { type: 'grid', properties: { rows: 1, - columns: 1, + columns: 2, }, }, - viewports: [ + displaySets: [ { - viewportSettings: [], + id: 'displaySet', imageMatchingRules: [], seriesMatchingRules: [], studyMatchingRules: [], }, ], - createdDate: '2021-02-23T19:22:08.894Z', + viewports: [ + { + viewportOptions: { + toolGroupId: 'default', + }, + displaySets: [ + { + options: [], + id: 'displaySet', + }, + ], + }, + ], + createdDate: '2021-02-23T18:32:42.850Z', }, ], numberOfPriorsReferenced: -1, @@ -51,8 +65,385 @@ function getHangingProtocolModule() { return [ { name: hangingProtocolName, - protocols: [deafultProtocol], + protocols: [defaultProtocol], }, ]; } ``` + + + +## Skeleton of A Hanging Protocol + +The skeleton of a hanging protocol is as follows: + + +```js +const testProtocol = { + id: 'test', + locked: true, + hasUpdatedPriorsInformation: false, + name: 'Default', + createdDate: '2021-02-23T19:22:08.894Z', + modifiedDate: '2021-02-23T19:22:08.894Z', + availableTo: {}, + editableBy: {}, + toolGroupIds: ['default'], + protocolMatchingRules: [ + { + id: 'wauZK2QNEfDPwcAQo', + weight: 1, + attribute: 'StudyDescription', + constraint: { + contains: { + value: 'PETCT', + }, + }, + required: false, + }, + { + id: 'wauZK2QNEfDPwcAQo', + weight: 1, + attribute: 'StudyDescription', + constraint: { + contains: { + value: 'PET/CT', + }, + }, + required: false, + }, + ], + stages: [ + { + id: 'hYbmMy3b7pz7GLiaT', + name: 'default', + viewportStructure: { + type: 'grid', + properties: { + rows: 1, + columns: 1, + viewportOptions: [], + }, + }, + displaySets: [ + { + id: 'ctDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'Modality', + constraint: { + equals: { + value: 'CT', + }, + }, + required: true, + }, + ], + studyMatchingRules: [], + }, + { + id: 'ptACDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'Modality', + constraint: { + equals: { + value: 'PT', + }, + }, + required: true, + }, + ], + studyMatchingRules: [], + }, + { + id: 'ptNACDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'SeriesDescription', + constraint: { + contains: { + value: 'Uncorrected', + }, + }, + required: false, + }, + ], + studyMatchingRules: [], + }, + ], + viewports: [ + { + viewportOptions: { + viewportId: 'ctAxial', + viewportType: 'stack', + background: [0, 0, 0], + orientation: 'AXIAL', + toolGroupId: 'default', + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ptAxial', + viewportType: 'stack', + background: [1, 1, 1], + orientation: 'AXIAL', + toolGroupId: 'default', + }, + displaySets: [ + { + options: { + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + voiInverted: true, + }, + id: 'ptACDisplaySet', + }, + ], + }, + ], + createdDate: '2021-02-23T18:32:42.850Z', + }, + ], + numberOfPriorsReferenced: -1, +}; +``` + +Let's discuss each property in depth. + +### Id +unique identifier for the protocol + +### protocolMatchingRules +A list of criteria for the protocol along with the provided points for ranking. + + - `weight`: weight for the matching rule. Eventually, all the registered + protocols get sorted based on the weights, and the winning protocol gets + applied to the viewer. + - `attribute`: tag that needs to be matched against. This can be either + Study-level metadata or a custom attribute. + [Learn more about custom attribute matching](#custom-attribute) + + - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be + [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] + + A sample of the matching rule is above which matches against the study description to be PETCT + + ```js + { + id: 'wauZK2QNEfDPwcAQo', + weight: 1, + attribute: 'StudyDescription', + constraint: { + contains: { + value: 'PETCT', + }, + }, + required: false, + }, + ``` + +### stages +Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules. + Therefore, the `stages` property is array of objects, each object being one stage. + +### viewportStructure +Defines the layout of the viewer. You can define the number of `rows` and `columns`. There should be `rows * columns` number of +viewport configuration in the `viewports` property. Note that order of viewports are rows first then columns. + +```js +viewportStructure: { + type: 'grid', + properties: { + rows: 1, + columns: 2, + viewportOptions: [], + }, +}, +``` + +In addition to the equal viewport sizes, you can define viewports to span multiple rows or columns. + +```js +viewportStructure: { + type: 'grid', + properties: { + rows: 1, + columns: 2, + viewportOptions: [ + { + x: 0, + y: 0, + width: 1 / 4, + height: 1, + }, + { + x: 1 / 4, + y: 0, + width: 3 / 4, + height: 1, + }, + ], + }, +}, +``` + +### displaySets +Defines the display sets that are available for the stage. Each +include an `id` and some `seriesMatchingRules` + +```js +displaySets: [ + { + id: 'ctDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'Modality', + constraint: { + equals: { + value: 'CT', + }, + }, + required: true, + }, + ], + studyMatchingRules: [], + }, + { + id: 'ptACDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'Modality', + constraint: { + equals: { + value: 'PT', + }, + }, + required: true, + }, + ], + studyMatchingRules: [], + }, + { + id: 'ptNACDisplaySet', + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'Modality', + constraint: { + equals: { + value: 'PT', + }, + }, + required: true, + }, + { + id: 'GPEYqFLv2dwzCM322', + weight: 1, + attribute: 'SeriesDescription', + constraint: { + contains: { + value: 'Uncorrected', + }, + }, + required: false, + }, + ], + studyMatchingRules: [], + }, +], +``` + +As you see in the above `displaySets` entry in the example above, there are +three defined displaySets that __will__ become available later on in the `viewports` +section of the protocol. + + +1. `ctDisplaySet` which is matched against the CT modality +2. `ptACDisplaySet` which is matched against the PT modality +3. `ptNACDisplaySet` which is matched against the PT modality and also contains the `Uncorrected` series description + +### viewports +This field includes the viewports that will get hung on the viewer. + +```js +viewports: [ + { + viewportOptions: { + viewportId: 'ctAxial', + viewportType: 'stack', + background: [0, 0, 0], + orientation: 'AXIAL', + toolGroupId: 'default', + }, + displaySets: [ + { + id: 'ctDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'ptAxial', + viewportType: 'stack', + background: [1, 1, 1], + orientation: 'AXIAL', + toolGroupId: 'default', + }, + displaySets: [ + { + options: { + voi: { + windowWidth: 5, + windowCenter: 2.5, + }, + voiInverted: true, + }, + id: 'ptACDisplaySet', + }, + ], + }, +], +``` + +As you can see there are two viewports defined. The first one is a CT viewport and the second one is a PT viewport. + +Each viewport has two properties: + +1. `viewportOptions`: defines the viewport properties such as + - `viewportId`: unique identifier for the viewport (optional) + - `viewportType`: type of the viewport (required - currently only stack) + - `background`: background color of the viewport (optional) + - `orientation`: orientation of the viewport (optional) + - `toolGroupId`: tool group that will be used for the viewport (optional) + + +2. `displaySets`: defines the display sets that are available for the viewport. It is an array of objects, each object being one display set. + - `id`: id of the display set (required) + - `options` (optional): options for the display set + - voi: windowing options for the display set + - voiInverted: whether the VOI is inverted or not (optional) diff --git a/platform/docs/docs/platform/extensions/modules/panel.md b/platform/docs/docs/platform/extensions/modules/panel.md index 55e1e82be10..1ad0d8c0813 100644 --- a/platform/docs/docs/platform/extensions/modules/panel.md +++ b/platform/docs/docs/platform/extensions/modules/panel.md @@ -72,7 +72,7 @@ const extensionDependencies = { '@ohif/extension-default': '^3.0.0', '@ohif/extension-cornerstone': '^3.0.0', '@ohif/extension-measurement-tracking': '^3.0.0', - '@ohif/extension-dicom-sr': '^3.0.0', + '@ohif/extension-cornerstone-dicom-sr': '^3.0.0', }; const id = 'viewer' diff --git a/platform/docs/docs/platform/extensions/modules/toolbar.md b/platform/docs/docs/platform/extensions/modules/toolbar.md index f340e0c4e2c..95ff0464986 100644 --- a/platform/docs/docs/platform/extensions/modules/toolbar.md +++ b/platform/docs/docs/platform/extensions/modules/toolbar.md @@ -89,25 +89,32 @@ The simplest toolbarButtons definition has the following properties: ```js { - id: 'Zoom', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-zoom', - label: 'Zoom', - commandOptions: { toolName: 'Zoom' }, - }, -}, + "id": "Zoom", + "type": "ohif.radioGroup", + "props": { + "type": "tool", + "icon": "tool-zoom", + "label": "Zoom", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "Zoom" + }, + "context": "CORNERSTONE" + } + ] + } +} ``` | property | description | values | | ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | | `id` | Unique string identifier for the definition | \* | -| `label` | User/display friendly to show in UI | \* | -| `icon` | A string name for an icon supported by the consuming application. | \* | | `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | -| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | -| `commandOptions` | (optional) Options to pass the target `commandName` | \* | +| `icon` | A string name for an icon supported by the consuming application. | \* | +| `label` | User/display friendly to show in UI | \* | +| `commands` | (optional) The commands to run when the button is used. It include a commandName, commandOptions, and/or a context | Any command registered by a `CommandModule` | There are three main types of toolbar buttons: @@ -133,59 +140,92 @@ to create `MeasurementTools` nested button ```js title="modes/longitudinal/src/toolbarButtons.js" { - id: 'MeasurementTools', - type: 'ohif.splitButton', - props: { - groupId: 'MeasurementTools', - isRadio: true, - primary: { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } + "id": "MeasurementTools", + "type": "ohif.splitButton", + "props": { + "groupId": "MeasurementTools", + "isRadio": true, + "primary": { + "id": "Length", + "icon": "tool-length", + "label": "Length", + "type": "tool", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "Length" + }, + "context": "CORNERSTONE" + }, + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "SRLength", + "toolGroupId": "SRToolGroup" + }, + "context": "CORNERSTONE" + } + ], + "tooltip": "Length" }, - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Measure Tools', + "secondary": { + "icon": "chevron-down", + "label": "", + "isActive": true, + "tooltip": "More Measure Tools" }, - items: [ - // Length tool - { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } - }, - // Bidirectional tool + "items": [ { - id: 'Bidirectional', - icon: 'tool-bidirectional', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Bidirectional', - } + "id": "Bidirectional", + "icon": "tool-bidirectional", + "label": "Bidirectional", + "type": "tool", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "Bidirectional" + }, + "context": "CORNERSTONE" + }, + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "SRBidirectional", + "toolGroupId": "SRToolGroup" + }, + "context": "CORNERSTONE" + } + ], + "tooltip": "Bidirectional Tool" }, - // Ellipse tool { - id: 'EllipticalRoi', - icon: 'tool-elipse', - label: 'Ellipse', - type: 'tool', - commandOptions: { - toolName: 'EllipticalRoi', - } + "id": "ArrowAnnotate", + "icon": "tool-annotate", + "label": "Annotation", + "type": "tool", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "ArrowAnnotate" + }, + "context": "CORNERSTONE" + }, + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "SRArrowAnnotate", + "toolGroupId": "SRToolGroup" + }, + "context": "CORNERSTONE" + } + ], + "tooltip": "Arrow Annotate" }, - ], - }, + ] + } } ``` diff --git a/platform/docs/docs/platform/extensions/modules/utility.md b/platform/docs/docs/platform/extensions/modules/utility.md new file mode 100644 index 00000000000..1d0f8e6748f --- /dev/null +++ b/platform/docs/docs/platform/extensions/modules/utility.md @@ -0,0 +1,57 @@ +--- +sidebar_position: 9 +sidebar_label: Utility +--- + +# Module: Utility + +## Overview + +Often, an extension will need to expose some useful functionality to the other +extensions, or modes that consume the extension. For example, the `cornerstone` +extension, uses its `utility` module to expose methods via + +```js +getUtilityModule({ servicesManager }) { + return [ + { + name: 'common', + exports: { + getCornerstoneLibraries: () => { + return { cornerstone, cornerstoneTools }; + }, + getEnabledElement, + CornerstoneViewportService, + dicomLoaderService, + }, + }, + { + name: 'core', + exports: { + Enums: cs3DEnums, + CONSTANTS, + }, + }, + { + name: 'tools', + exports: { + toolNames, + Enums: cs3DToolsEnums, + }, + }, + ]; + }, +}; +``` + +Then a consuming extension can use `getModuleEntry` to access the methods +Below, which is a code from `TrackedCornerstoneViewport` use the `getUtilityModule` method to get the internal `CornerstoneViewportService` which handles the `Cornerstone` viewport. + +```js title="extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.tsx" +const utilityModule = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.utilityModule.common' +); + +const { CornerstoneViewportService } = utilityModule.exports; +const viewportId = CornerstoneViewportService.getViewportId(viewportIndex); +``` diff --git a/platform/docs/docs/platform/extensions/modules/viewport.md b/platform/docs/docs/platform/extensions/modules/viewport.md index b49b33f6661..163a9e11dfd 100644 --- a/platform/docs/docs/platform/extensions/modules/viewport.md +++ b/platform/docs/docs/platform/extensions/modules/viewport.md @@ -14,10 +14,7 @@ add support for: - 2D Medical Image Viewing (cornerstone ext.) - Structured Reports as SR (DICOM SR ext.) -- Structured Reports as HTML (DICOM html ext.) - Encapsulated PDFs as PDFs (DICOM pdf ext.) -- Whole Slide Microscopy Viewing (whole slide ext.) -- etc. The general pattern is that a mode can define which `Viewport` to use for which specific `SOPClassHandlerUID`, so if you want to fork just a single Viewport @@ -43,44 +40,53 @@ const getViewportModule = () => { ## Example Viewport Component -A simplified version of the tracked CornerstoneViewport is shown below, which -creates a cornerstone viewport and action bar on top of it. +A simplified version of the tracked `OHIFCornerstoneViewport` is shown below, which +creates a cornerstone viewport: + +:::note Tip + +Not in OHIF version 3.1 we use `displaySets` in the props which is new compared to +the previous version (3.0) which uses `displaySet`. This is due to the fact that +we are moving to a new data model that can render fused images in a single viewport. + +::: ```jsx function TrackedCornerstoneViewport({ children, dataSource, - displaySet, + displaySets, viewportIndex, servicesManager, extensionManager, commandsManager, }) { - const renderViewport = () => { - const { component: Component } = extensionManager.getModuleEntry( - '@ohif/extension-cornerstone.viewportModule.cornerstone' - ); - return ( - - ); - }; return ( - <> - -
- {renderViewport()} -
- +
+ /** Resize Detector */ + + /** Div For displaying image */ +
e.preventDefault()} + onMouseDown={e => e.preventDefault()} + ref={elementRef} + >
+
); } ``` -![viewportModule](../../../assets/img/viewportModule.png) ### `@ohif/viewer` diff --git a/platform/docs/docs/platform/managers/service.md b/platform/docs/docs/platform/managers/service.md index d60ec83e109..2cacda412e8 100644 --- a/platform/docs/docs/platform/managers/service.md +++ b/platform/docs/docs/platform/managers/service.md @@ -2,24 +2,30 @@ sidebar_position: 3 sidebar_label: Service Manager --- + # Services Manager ## Overview -Services manager is the single point of service registration. Each service needs to implement a `create` method which gets called inside `ServicesManager` to instantiate the service. In the app, you can get access to a registered service via the `services` property of the `ServicesManager`. + +Services manager is the single point of service registration. Each service needs +to implement a `create` method which gets called inside `ServicesManager` to +instantiate the service. In the app, you can get access to a registered service +via the `services` property of the `ServicesManager`. ## Skeleton -*Simplified* skeleton of `ServicesManager` is shown below. There are two public methods: + +_Simplified_ skeleton of `ServicesManager` is shown below. There are two public +methods: - `registerService`: registering a new service with/without a configuration - `registerServices`: registering batch of services - ```js export default class ServicesManager { constructor(commandsManager) { - this._commandsManager = commandsManager - this.services = {} - this.registeredServiceNames = [] + this._commandsManager = commandsManager; + this.services = {}; + this.registeredServiceNames = []; } registerService(service, configuration = {}) { @@ -27,10 +33,10 @@ export default class ServicesManager { this.services[service.name] = service.create({ configuration, commandsManager: this._commandsManager, - }) + }); /* Track service registration */ - this.registeredServiceNames.push(service.name) + this.registeredServiceNames.push(service.name); } registerServices(services) { @@ -39,32 +45,32 @@ export default class ServicesManager { } ``` - ## Default Registered Services + By default, `OHIF-v3` registers the following services in the `appInit`. ```js title="platform/viewer/src/appInit.js" servicesManager.registerServices([ - UINotificationService, - UIModalService, - UIDialogService, - UIViewportDialogService, - MeasurementService, - DisplaySetService, - ToolBarService, - ViewportGridService, - HangingProtocolService, - CineService, - ]); + UINotificationService, + UIModalService, + UIDialogService, + UIViewportDialogService, + MeasurementService, + DisplaySetService, + ToolBarService, + ViewportGridService, + HangingProtocolService, + CineService, +]); ``` - - ## Service Architecture + If you take a look at the folder of each service implementation above, you will -find out that services need to be exported as an object with `name` and `create` method. +find out that services need to be exported as an object with `name` and `create` +method. -For instance, `ToolbarService` is exported as: +For instance, `ToolBarService` is exported as: ```js title="platform/core/src/services/ToolBarService/index.js" import ToolBarService from './ToolBarService'; @@ -77,43 +83,45 @@ export default { }; ``` -and the implementation of `ToolbarService` lies in the same folder at `./ToolbarSerivce.js`. +and the implementation of `ToolBarService` lies in the same folder at +`./ToolbarSerivce.js`. > Note, the create method is critical for any custom service that you write and > want to add to the list of services - - ## Accessing Services -Throughout the app you can use `services` property of the service manager to access -the desired service. -For instance in the `PanelMeasurementTableTracking` which is the right panel in the -`longitudinal` mode, we have the *simplified code below* for downloading the drawn measurements. +Throughout the app you can use `services` property of the service manager to +access the desired service. + +For instance in the `PanelMeasurementTableTracking` which is the right panel in +the `longitudinal` mode, we have the _simplified code below_ for downloading the +drawn measurements. ```js function PanelMeasurementTableTracking({ servicesManager }) { - const { MeasurementService } = servicesManager.services + const { MeasurementService } = servicesManager.services; /** ... **/ async function exportReport() { - const measurements = MeasurementService.getMeasurements() + const measurements = MeasurementService.getMeasurements(); /** ... **/ - downloadCSVReport(measurements, MeasurementService) + downloadCSVReport(measurements, MeasurementService); } /** ... **/ - return <> /** ... **/ + return <> /** ... **/ ; } ``` - - ## Registering Custom Services -You might need to write you own custom service in an extension. `preRegistration` hook inside your extension is the place for registering your custom service. + +You might need to write you own custom service in an extension. +`preRegistration` hook inside your extension is the place for registering your +custom service. ```js title="extensions/customExtension/src/index.js" -import WrappedBackEndService from './services/backEndService' +import WrappedBackEndService from './services/backEndService'; export default { // ID of the extension @@ -121,12 +129,11 @@ export default { preRegistration({ servicesManager }) { servicesManager.registerService(WrappedBackEndService(servicesManager)); }, -} +}; ``` and the logic for your service shall be - ```js title="extensions/customExtension/src/services/backEndService/index.js" import backEndService from './backEndService'; diff --git a/platform/docs/docs/platform/modes/lifecycle.md b/platform/docs/docs/platform/modes/lifecycle.md index 0e5ef9955bc..4993c30f514 100644 --- a/platform/docs/docs/platform/modes/lifecycle.md +++ b/platform/docs/docs/platform/modes/lifecycle.md @@ -22,6 +22,15 @@ For instance, in `longitudinal` mode we are using this hook to initialize the `ToolBarService` and set the window level/width tool to be active and add buttons to the toolbar. +:::note Tip + +In OHIF Version 3.1, there is a new service `ToolGroupService` that is used to +define and manage tools for the group of viewports. This is a new concept +borrowed from the Cornerstone ToolGroup, and you can read more +[here](https://www.cornerstonejs.org/docs/concepts/cornerstone-tools/toolgroups/) + +::: + ```js function modeFactory() { return { @@ -29,16 +38,10 @@ function modeFactory() { version: '', displayName: '', onModeEnter: ({ servicesManager, extensionManager }) => { - const { ToolBarService } = servicesManager.services; - - const interaction = { - groupId: 'primary', - itemId: 'Wwwc', - interactionType: 'tool', - commandOptions: undefined, - }; + const { ToolBarService, ToolGroupService } = servicesManager.services; - ToolBarService.recordInteraction(interaction); + // Init Default and SR ToolGroups + initToolGroups(extensionManager, ToolGroupService); ToolBarService.init(extensionManager); ToolBarService.addButtons(toolbarButtons); @@ -65,7 +68,7 @@ This hook is called when the viewer navigate away from the route in the url. This is the place for cleaning up data, and services by unsubscribing to the events. -For instance, it can be used to reset the `ToolbarService` which reset the +For instance, it can be used to reset the `ToolBarService` which reset the toggled buttons. ```js diff --git a/platform/docs/docs/platform/modes/routes.md b/platform/docs/docs/platform/modes/routes.md index 1a149decf68..2204bd77ae7 100644 --- a/platform/docs/docs/platform/modes/routes.md +++ b/platform/docs/docs/platform/modes/routes.md @@ -58,9 +58,9 @@ function modeFactory() { ], }, { - namespace: '@ohif/extension-dicom-sr.viewportModule.dicom-sr', + namespace: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', displaySetsToDisplay: [ - '@ohif/extension-dicom-sr.sopClassHandlerModule.dicom-sr', + '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', ], }, ], @@ -204,9 +204,6 @@ init: async ({ const instance = displaySet.images.find( image => image.SOPInstanceUID === tool.SOPInstanceUID ); - - const { SOPInstanceUID, url } = instance; - displaySet.initialImageIdIndex = displaySet.images.indexOf(instance); }); MeasurementService.addMeasurement(/**...**/); diff --git a/platform/docs/docs/platform/services/data/HangingProtocolService.md b/platform/docs/docs/platform/services/data/HangingProtocolService.md index 269e409c2eb..85ed08b34c8 100644 --- a/platform/docs/docs/platform/services/data/HangingProtocolService.md +++ b/platform/docs/docs/platform/services/data/HangingProtocolService.md @@ -2,205 +2,54 @@ sidebar_position: 4 sidebar_label: Hanging Protocol Service --- + # Hanging Protocol Service ## Overview -`HangingProtocolService` is a migration of the `OHIF-v1` hanging protocol engine. -This service handles the arrangement of the images in the viewport. -In short, the registered protocols will get matched with the Series that are available -for the series. Each protocol gets a point, and they are ranked. The winning protocol gets -applied and its settings run for the viewports. -You can read more about hanging protocols [here](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf). In short -with `OHIF-v3` hanging protocols you can: +`HangingProtocolService` is a migration of the `OHIF-v1` hanging protocol +engine. This service handles the arrangement of the images in the viewport. In +short, the registered protocols will get matched with the Series that are +available for the series. Each protocol gets a point, and they are ranked. The +winning protocol gets applied and its settings run for the viewports. + +You can read more about hanging protocols +[here](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf). +In short with `OHIF-v3` hanging protocols you can: - Define what layout of the viewport should the viewer starts with (2x2 layout) - Define which series gets displayed in which position of the layout - Apply certain initial viewport settings; e.g., inverting the contrast -- Enable certain tools based on what series are displayed: link prostate T2 and ADC MRI. - - - - -## Skeleton of A Hanging Protocol -You can find the skeleton of the hanging protocols here: - -```js -const deafultProtocol = { - id: 'defaultProtocol', - locked: true, - hasUpdatedPriorsInformation: false, - name: 'Default', - createdDate: '2021-02-23T19:22:08.894Z', - modifiedDate: '2021-02-23T19:22:08.894Z', - availableTo: {}, - editableBy: {}, - protocolMatchingRules: [], - stages: [ - { - id: 'nwzau7jDkEkL8djfr', - name: 'oneByOne', - viewportStructure: { - type: 'grid', - properties: { - rows: 1, - columns: 1, - }, - }, - viewports: [ - { - viewportSettings: [], - imageMatchingRules: [], - seriesMatchingRules: [], - studyMatchingRules: [], - }, - ], - createdDate: '2021-02-23T19:22:08.894Z', - }, - ], - numberOfPriorsReferenced: -1, -}; -``` - -Let's discuss each property in depth. - - - - -- `id`: unique identifier for the protocol - -- `protocolMatchingRules`: A list of criteria for the protocol along with the provided points for ranking. - - - `weight`: weight for the matching rule. Eventually, all the registered protocols get sorted based on the weights, and the winning - protocol gets applied to the viewer. - - `attriubte`: tag that needs to be matched against. This can be either Study-level metadata or a custom attribute. [Learn more about custom attribute matching](#custom-attribute) - - - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be - [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] - - A sample of the matching rule is: - - ```js - { - id: 'wauZK2QNEfDPwcAQo', - weight: 1, - attribute: 'StudyInstanceUID', - constraint: { - equals: { - value: '1.3.6.1.4.1.25403.345050719074.3824.20170125112931.11', - }, - }, - required: true, - } - ``` - -- `stages`: Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules. - Therefore, the `stages` property is array of objects, each object being one stage. - - - `viewportStructure`: Defines the layout of the viewer. You can define the number of `rows` and `columns`. - There should be `rows * columns` number of viewport configuration in the `viewports` property. Note that order of viewports are rows first then columns. - - - `viewportSettings`: custom settings to be applied to the viewport. This can be a `voi` being applied - to the viewer or a tool to get enabled. We will discuss viewport-specific settings [below](#viewport-settings) - - - `imageMatchingRules (comming soon)`: setting the image slice for the viewport. - - - `seriesMatchingRules`: the most important rule that matches series in the viewport. For instance, the following stage - configuration will create a one-by-two layout and put the series whose description contains `t2` on the left, and a series with - description that contains `adc` on the right. (order of viewports are rows, first then columns) - - ```js - stages: [ - { - id: 'hYbmMy3b7pz7GLiaT', - name: 'oneByThree', - viewportStructure: { - type: 'grid', - properties: { - rows: 1, - columns: 2, - }, - }, - viewports: [ - // viewport 1 - { - viewportSettings: [], - imageMatchingRules: [], - seriesMatchingRules: [ - { - id: 'vSjk7NCYjtdS3XZAw', - weight: 1, - attribute: 'SeriesDescription', - constraint: { - contains: { - value: 't2', - }, - }, - required: false, - }, - ], - studyMatchingRules: [], - }, - // viewport 2 - { - viewportSettings: [], - imageMatchingRules: [], - seriesMatchingRules: [ - { - id: 'vSjk7NCYjtdS3XZAw', - weight: 1, - attribute: 'SeriesDescription', - constraint: { - contains: { - value: 'ADC', - }, - }, - required: true, - }, - ], - studyMatchingRules: [], - }, - ], - }, - ] - ``` - - - - - - +- Enable certain tools based on what series are displayed: link prostate T2 and + ADC MRI. ## Events -There are two events that get publish in `HangingProtocolService`: +There are two events that get publish in `HangingProtocolService`: | Event | Description | | ------------ | -------------------------------------------------------------------- | | NEW_LAYOUT | Fires when a new layout is requested by the `HangingProtocolService` | | STAGE_CHANGE | Fires when the the stage is changed in the hanging protocols | - ## API - `getState`: returns an array: `[matchDetails, hpAlreadyApplied]`: - `matchDetails`: matching details for the series - - `hpAlreadyApplied`: An array which tracks whether HPServices has been applied on each viewport. + - `hpAlreadyApplied`: An array which tracks whether HPServices has been + applied on each viewport. -- `addProtocols`: adds provided protocols to the list of registered protocols for matching +- `addProtocols`: adds provided protocols to the list of registered protocols + for matching -- `run(studyMetaData, protocol)`: runs the HPService with the provided studyMetaData and optional protocol. - If protocol is not given, HP Matching engine will search all the registered protocols for the best matching one based on the constraints. +- `run(studyMetaData, protocol)`: runs the HPService with the provided + studyMetaData and optional protocol. If protocol is not given, HP Matching + engine will search all the registered protocols for the best matching one + based on the constraints. - `addCustomAttribute`: adding a custom attribute for matching. (see below) -- `addCustomViewportSetting`: adding a custom setting to a viewport (initial `voi`). Below, we explain - in detail how to add custom viewport settings via protocol definitions. `addCustomViewportSetting` is another way to set - these settings which is exposed by API - -- - Default initialization of the modes handles running the `HangingProtocolService` ## Custom Attribute @@ -208,9 +57,13 @@ In some situations, you might want to match based on a custom attribute and not if you have assigned a `timepointId` to each study, and you want to match based on it. Good news is that, in `OHIF-v3` you can define you custom attribute and use it for matching. -There are various ways that you can let `HangingProtocolService` know of you custom attribute. -We will show how to add it inside the mode configuration. +In some situations, you might want to match based on a custom attribute and not +the DICOM tags. For instance, if you have assigned a `timepointId` to each study +and you want to match based on it. Good news is that, in `OHIF-v3` you can +define you custom attribute and use it for matching. +There are various ways that you can let `HangingProtocolService` know of you +custom attribute. We will show how to add it inside the mode configuration. ```js const deafultProtocol = { @@ -233,13 +86,13 @@ const deafultProtocol = { /** ... **/ ], numberOfPriorsReferenced: -1, -} +}; // Custom function for custom attribute -const getTimePointUID = (metaData) => { +const getTimePointUID = metaData => { // requesting the timePoint Id - return myBackEndAPI(metaData) -} + return myBackEndAPI(metaData); +}; function modeFactory() { return { @@ -249,49 +102,51 @@ function modeFactory() { { path: 'myModeRoute', init: async ({}) => { - const { DicomMetadataStore, HangingProtocolService } = - servicesManager.services + const { + DicomMetadataStore, + HangingProtocolService, + } = servicesManager.services; const onSeriesAdded = ({ StudyInstanceUID, madeInClient = false, }) => { - const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID) + const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); // Adding custom attribute to the hangingprotocol HangingProtocolService.addCustomAttribute( 'timepoint', 'timepoint', - (metaData) => getFirstMeasurementSeriesInstanceUID(metaData) - ) + metaData => getFirstMeasurementSeriesInstanceUID(metaData) + ); - HangingProtocolService.run(studyMetadata) - } + HangingProtocolService.run(studyMetadata); + }; DicomMetadataStore.subscribe( DicomMetadataStore.EVENTS.SERIES_ADDED, onSeriesAdded - ) + ); }, }, ], /** ... **/ - } + }; } ``` - ## Viewport Settings -You can define custom settings to be applied to each viewport. There -are two types of settings: + +You can define custom settings to be applied to each viewport. There are two +types of settings: - `viewport settings`: Currently we support two viewport settings - `voi`: applying an initial `voi` by setting the windowWidth and windowCenter - `inverted`: inverting the viewport color (e.g., for PET images) -- `props settings`: Running commands after the first render; e.g., enabling the link tool - +- `props settings`: Running commands after the first render; e.g., enabling the + link tool Examples of each settings are : @@ -307,7 +162,7 @@ viewportSettings: [ commandName: 'setToolActive', type: 'props', }, -] +]; ``` and @@ -324,5 +179,5 @@ viewportSettings: [ commandName: '', type: 'viewport', }, -] +]; ``` diff --git a/platform/docs/docs/platform/services/data/ToolbarService.md b/platform/docs/docs/platform/services/data/ToolbarService.md index e9ab5a94553..18e7b7afb1d 100644 --- a/platform/docs/docs/platform/services/data/ToolbarService.md +++ b/platform/docs/docs/platform/services/data/ToolbarService.md @@ -7,7 +7,7 @@ sidebar_label: Toolbar Service ## Overview -`ToolbarService` handles the toolbar section buttons, and what happens when a +`ToolBarService` handles the toolbar section buttons, and what happens when a button is clicked by the user.
@@ -19,12 +19,12 @@ button is clicked by the user. | Event | Description | | ----------------------- | ---------------------------------------------------------------------- | | TOOL_BAR_MODIFIED | Fires when a button is added/removed to the toolbar | -| TOOL_BAR_STATE_MODIFIED | Fires when an interaction happens and ToolbarService state is modified | +| TOOL_BAR_STATE_MODIFIED | Fires when an interaction happens and ToolBarService state is modified | ## API - `recordInteraction(interaction)`: executes the provided interaction which is - an object providing the following properties to the ToolbarService: + an object providing the following properties to the ToolBarService: - `interactionType`: can be `tool`, `toggle` and `action`. We will discuss more each type below. @@ -47,7 +47,7 @@ button is clicked by the user. ## State -ToolbarService has an internal state that gets updated per tool interaction and +ToolBarService has an internal state that gets updated per tool interaction and tracks the active toolId, state of the buttons that have toggled state, and the group buttons and which tool in each group is active. @@ -70,10 +70,10 @@ interaction object. - `tool`: setting a tool to be active; e.g., measurement tools - `toggle`: toggling state of a tool; e.g., viewport link (sync) -- `action`: performs a registered action outside of the ToolbarService; e.g., +- `action`: performs a registered action outside of the ToolBarService; e.g., capture -A _simplified_ implementation of the ToolbarService is: +A _simplified_ implementation of the ToolBarService is: ```js export default class ToolBarService { @@ -115,25 +115,32 @@ The simplest toolbarButtons definition has the following properties: ```js { - id: 'Zoom', - type: 'ohif.radioGroup', - props: { - type: 'tool', - icon: 'tool-zoom', - label: 'Zoom', - commandOptions: { toolName: 'Zoom' }, - }, -}, + "id": "Zoom", + "type": "ohif.radioGroup", + "props": { + "type": "tool", + "icon": "tool-zoom", + "label": "Zoom", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "Zoom" + }, + "context": "CORNERSTONE" + } + ] + } +} ``` | property | description | values | | ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | | `id` | Unique string identifier for the definition | \* | -| `label` | User/display friendly to show in UI | \* | -| `icon` | A string name for an icon supported by the consuming application. | \* | | `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | -| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | -| `commandOptions` | (optional) Options to pass the target `commandName` | \* | +| `icon` | A string name for an icon supported by the consuming application. | \* | +| `label` | User/display friendly to show in UI | \* | +| `commands` | (optional) The commands to run when the button is used. It include a commandName, commandOptions, and/or a context | Any command registered by a `CommandModule` | There are three main types of toolbar buttons: @@ -159,58 +166,91 @@ to create `MeasurementTools` nested button ```js title="modes/longitudinal/src/toolbarButtons.js" { - id: 'MeasurementTools', - type: 'ohif.splitButton', - props: { - groupId: 'MeasurementTools', - isRadio: true, - primary: { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } + "id": "MeasurementTools", + "type": "ohif.splitButton", + "props": { + "groupId": "MeasurementTools", + "isRadio": true, + "primary": { + "id": "Length", + "icon": "tool-length", + "label": "Length", + "type": "tool", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "Length" + }, + "context": "CORNERSTONE" + }, + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "SRLength", + "toolGroupId": "SRToolGroup" + }, + "context": "CORNERSTONE" + } + ], + "tooltip": "Length" }, - secondary: { - icon: 'chevron-down', - label: '', - isActive: true, - tooltip: 'More Measure Tools', + "secondary": { + "icon": "chevron-down", + "label": "", + "isActive": true, + "tooltip": "More Measure Tools" }, - items: [ - // Length tool + "items": [ { - id: 'Length', - icon: 'tool-length', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Length', - } + "id": "Bidirectional", + "icon": "tool-bidirectional", + "label": "Bidirectional", + "type": "tool", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "Bidirectional" + }, + "context": "CORNERSTONE" + }, + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "SRBidirectional", + "toolGroupId": "SRToolGroup" + }, + "context": "CORNERSTONE" + } + ], + "tooltip": "Bidirectional Tool" }, - // Bidirectional tool { - id: 'Bidirectional', - icon: 'tool-bidirectional', - label: 'Length', - type: 'tool', - commandOptions: { - toolName: 'Bidirectional', - } + "id": "ArrowAnnotate", + "icon": "tool-annotate", + "label": "Annotation", + "type": "tool", + "commands": [ + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "ArrowAnnotate" + }, + "context": "CORNERSTONE" + }, + { + "commandName": "setToolActive", + "commandOptions": { + "toolName": "SRArrowAnnotate", + "toolGroupId": "SRToolGroup" + }, + "context": "CORNERSTONE" + } + ], + "tooltip": "Arrow Annotate" }, - // Ellipse tool - { - id: 'EllipticalRoi', - icon: 'tool-elipse', - label: 'Ellipse', - type: 'tool', - commandOptions: { - toolName: 'EllipticalRoi', - } - }, - ], - }, + ] + } } ``` diff --git a/platform/docs/docs/platform/services/data/index.md b/platform/docs/docs/platform/services/data/index.md index a37f3db1e10..fa9128ae4da 100644 --- a/platform/docs/docs/platform/services/data/index.md +++ b/platform/docs/docs/platform/services/data/index.md @@ -17,7 +17,7 @@ We maintain the following non-ui Services: - [DicomMetadata Store](./../data/DicomMetadataStore.md) - [DisplaySet Service](./../data/DisplaySetService.md) - [Hanging Protocol Service](../data/HangingProtocolService.md) -- [Toolbar Service](../data/ToolbarService.md) +- [Toolbar Service](../data/ToolBarService.md) - [Measurement Service](../data/MeasurementService.md) ## Service Architecture diff --git a/platform/docs/docs/platform/services/index.md b/platform/docs/docs/platform/services/index.md index e3735c3bc59..87d88e7ad2b 100644 --- a/platform/docs/docs/platform/services/index.md +++ b/platform/docs/docs/platform/services/index.md @@ -83,7 +83,7 @@ The following services is available in the `OHIF-v3`. - + ToolBarService (NEW) diff --git a/platform/docs/docs/platform/services/ui/viewport-grid-service.md b/platform/docs/docs/platform/services/ui/viewport-grid-service.md index 06e048a2f8e..d48ab4e00e5 100644 --- a/platform/docs/docs/platform/services/ui/viewport-grid-service.md +++ b/platform/docs/docs/platform/services/ui/viewport-grid-service.md @@ -2,27 +2,25 @@ sidebar_position: 6 sidebar_label: Viewport Grid Service --- -# Viewport Grid Service +# Viewport Grid Service ## Overview -This is a new UI service, that handles the grid layout of the viewer. - - +This is a new UI service, that handles the grid layout of the viewer. ## Interface For a more detailed look on the options and return values each of these methods is expected to support, [check out it's interface in `@ohif/core`][interface] -| API Member | Description | -| -------------------------------------------------------------------- | --------------------------------------------------- | -| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | -| `getState()` | Gets the states of the viewport (see below) | -| `setDisplaysetForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | -| `setLayout({numCols, numRows})` | Sets rows and columns | -| `reset()` | Resets the default states | +| API Member | Description | +| --------------------------------------------------------------------- | --------------------------------------------------- | +| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | +| `getState()` | Gets the states of the viewport (see below) | +| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | +| `setLayout({numCols, numRows})` | Sets rows and columns | +| `reset()` | Resets the default states | ## Implementations @@ -32,7 +30,6 @@ is expected to support, [check out it's interface in `@ohif/core`][interface] `*` - Denotes maintained by OHIF - ## State ```js @@ -49,5 +46,5 @@ const DEFAULT_STATE = { */ ], activeViewportIndex: 0, -} +}; ``` diff --git a/platform/docs/docs/release-notes.md b/platform/docs/docs/release-notes.md index 242221bd7d9..abda85612e8 100644 --- a/platform/docs/docs/release-notes.md +++ b/platform/docs/docs/release-notes.md @@ -25,11 +25,12 @@ of our medical image web viewers from the start. The summary of changes includes - Our vision is that technical people focus of developing extensions which provides core functionalities, and experts to build modes by picking the appropriate functionalities from each extension. + - Migrated all the `cornerstone-core` and `cornerstone-tools` usage to the newly released `Cornerstone3D`. * UI has been completely redesigned with modularity and workflow modes in mind. * New UI components have been built with Tailwind CSS * Redux store has been removed from the viewer in favour of services backed by - React's Context API + React's Context **API** Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: @@ -86,6 +87,10 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: White-labelling: Easily replace the OHIF Logo with your logo ✅ + + + DICOM PDF support + ✅ DICOM Whole-slide imaging viewport @@ -97,11 +102,6 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: 🔜 Not Started - - DICOM PDF support - 🔜 - Not Started - Displaying non-renderable DICOM as HTML 🔜 @@ -129,8 +129,8 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: VTK Extension + MIP / MPR layout - ❌ - Other plans that involves amazing news soon! + 🔜 + 3D rendering and 3D annotation tools via Cornerstone3D UMD Build (Embedded Viewer). diff --git a/platform/docs/docs/resources.md b/platform/docs/docs/resources.md index be7b10ed756..65c968ffe62 100644 --- a/platform/docs/docs/resources.md +++ b/platform/docs/docs/resources.md @@ -11,6 +11,15 @@ and other resources that we have provided to the community in the past: ## 2022 +### The Imaging Network Ontario - Remote + +The Imaging Network Ontario (ImNO) is an annual symposium that brings together +medical imaging researchers and scientists from across Canada to share +knowledge, ideas, and experiences. +[[Slides]](https://docs.google.com/presentation/d/18XZDon4-Sitc2a70V5sFyhyUVZI_mIgfXHGtdxhZMjE/edit?usp=sharing) +[[Video]](https://vimeo.com/691134870/ad7d308a44) + + ### [NA-MIC Project Week 36th 2022 - Remote](https://github.com/NA-MIC/ProjectWeek/blob/master/PW36_2022_Virtual/README.md) The Project Week is a week-long hackathon of hands-on activity in which medical diff --git a/platform/docs/docusaurus.config.js b/platform/docs/docusaurus.config.js index 4a8cf9a20d6..36dfc96b112 100644 --- a/platform/docs/docusaurus.config.js +++ b/platform/docs/docusaurus.config.js @@ -7,26 +7,32 @@ const path = require('path'); const versions = require('./versions.json'); +const VersionsArchived = require('./versionsArchived.json'); + +const ArchivedVersionsDropdownItems = Object.entries(VersionsArchived).splice( + 0, + 5 +); // This probably only makes sense for the beta phase, temporary -function getNextBetaVersionName() { - const expectedPrefix = ''; +// function getNextBetaVersionName() { +// const expectedPrefix = ''; - const lastReleasedVersion = versions[0]; - if (!lastReleasedVersion.includes(expectedPrefix)) { - throw new Error( - 'this code is only meant to be used during the 2.0 beta phase.' - ); - } - const version = parseInt(lastReleasedVersion.replace(expectedPrefix, ''), 10); - return `${expectedPrefix}${version + 1}`; -} +// const lastReleasedVersion = versions[0]; +// if (!lastReleasedVersion.includes(expectedPrefix)) { +// throw new Error( +// 'this code is only meant to be used during the 2.0 beta phase.' +// ); +// } +// const version = parseInt(lastReleasedVersion.replace(expectedPrefix, ''), 10); +// return `${expectedPrefix}${version + 1}`; +// } -const allDocHomesPaths = [ - '/docs/', - '/docs/next/', - ...versions.slice(1).map(version => `/docs/${version}/`), -]; +// const allDocHomesPaths = [ +// '/docs/', +// '/docs/next/', +// ...versions.slice(1).map(version => `/docs/${version}/`), +// ]; const isDev = process.env.NODE_ENV === 'development'; @@ -44,7 +50,7 @@ const isI18nStaging = process.env.I18N_STAGING === 'true'; // const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging; /** @type {import('@docusaurus/types').DocusaurusConfig} */ -(module.exports = { +module.exports = { title: 'OHIF', tagline: 'Open-source web-based medical imaging platform', organizationName: 'Open Health Imaging Foundation', @@ -56,11 +62,11 @@ const isI18nStaging = process.env.I18N_STAGING === 'true'; defaultLocale: 'en', locales: isDeployPreview ? // Deploy preview: keep it fast! - ['en'] + ['en'] : isI18nStaging - ? // Staging locales: https://docusaurus-i18n-staging.netlify.app/ + ? // Staging locales: https://docusaurus-i18n-staging.netlify.app/ ['en'] - : // Production locales + : // Production locales ['en'], }, onBrokenLinks: 'warn', @@ -74,6 +80,12 @@ const isI18nStaging = process.env.I18N_STAGING === 'true'; plugins: [ path.resolve(__dirname, './pluginOHIFWebpackConfig.js'), 'plugin-image-zoom', // 3rd party plugin for image click to pop + [ + '@docusaurus/plugin-google-gtag', + { + trackingID: 'UA-110573590-2', + }, + ], [ '@docusaurus/plugin-client-redirects', { @@ -191,8 +203,9 @@ const isI18nStaging = process.env.I18N_STAGING === 'true'; ], presets: [ [ - "@docusaurus/preset-classic", - { + 'classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ debug: true, // force debug plugin usage docs: { routeBasePath: '/', @@ -206,7 +219,7 @@ const isI18nStaging = process.env.I18N_STAGING === 'true'; // We want users to submit doc updates to the upstream/next version! // Otherwise we risk losing the update on the next release. const nextVersionDocsDirPath = 'docs'; - return `https://github.com/OHIF/Viewers/edit/master/website/${nextVersionDocsDirPath}/${docPath}`; + return `https://github.com/OHIF/Viewers/edit/v3-stable/platform/docs/${docPath}`; }, showLastUpdateAuthor: true, showLastUpdateTime: true, @@ -221,185 +234,206 @@ const isI18nStaging = process.env.I18N_STAGING === 'true'; // : undefined, versions: { current: { - label: 'Version 3.0 🚧', + label: 'Version 3.1 - Cornerstone3D 🚧', }, '2.0': { - label: 'Version 2.0', + label: 'Version 2.0 - Master branch', //path: `2.0`, }, - '1.0': { - label: 'Version 1.0', - //path: `1.0`, - }, }, }, theme: { customCss: [require.resolve('./src/css/custom.css')], }, - }, + }), ], ], - themeConfig: { - liveCodeBlock: { - playgroundPosition: 'bottom', - }, - hideableSidebar: false, - colorMode: { - defaultMode: 'dark', - disableSwitch: false, - // respectPrefersColorScheme: true, - }, - /* + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + liveCodeBlock: { + playgroundPosition: 'bottom', + }, + docs: { + sidebar: { + hideable: true, + autoCollapseCategories: true, + }, + }, + colorMode: { + defaultMode: 'dark', + disableSwitch: false, + // respectPrefersColorScheme: true, + }, + /* announcementBar: { id: 'supportus', content: '⭐️ If you like Docusaurus, give it a star on GitHub! ⭐️', }, */ - prism: { - theme: require('prism-react-renderer/themes/github'), - darkTheme: require('prism-react-renderer/themes/dracula'), - }, - // metadatas: [{name: 'twitter:card', content: 'summary'}], - gtag: { - trackingID: 'UA-110573590-2', - }, - algolia: { - apiKey: '1960250e38c7655d2bfe0ce8fdaed987', - indexName: 'ohif', - contextualSearch: true, - }, - navbar: { - hideOnScroll: false, - logo: { - alt: 'OHIF Logo', - src: 'img/ohif-logo-light.svg', - srcDark: 'img/ohif-logo.svg', + prism: { + theme: require('prism-react-renderer/themes/github'), + darkTheme: require('prism-react-renderer/themes/dracula'), }, - items: [ - { - to: 'https://ohif.org/get-started', - label: 'Get Started', - target: '_self', - position: 'left', - }, - { - to: 'https://ohif.org/examples', - label: 'Examples', - target: '_self', - position: 'left', - }, - { - position: 'left', - to: '/', - activeBaseRegex: '^(/next/|/)$', - docId: 'Introduction', - label: 'Docs', - }, - { - to: 'https://ohif.org/community', - label: 'Community', - target: '_self', - position: 'left', - }, - { - to: '/help', - //activeBaseRegex: '(^/help$)|(/help)', - label: 'Help', - position: 'right', - }, - // { to: 'https://react.ohif.org/', label: 'UI Component Library', position: 'left' }, - // {to: 'showcase', label: 'Showcase', position: 'left'}, - // right - { - type: 'docsVersionDropdown', - position: 'right', - dropdownActiveClassDisabled: true, - }, - { - type: 'localeDropdown', - position: 'right', - dropdownItemsAfter: [ - { - to: '/platform/internationalization', - label: 'Help Us Translate', - }, - ], - }, - { - to: 'https://github.com/OHIF/Viewers', - position: 'right', - className: 'header-github-link', - 'aria-label': 'GitHub Repository', + algolia: { + appId: 'EFLT6YIHHZ', + apiKey: 'c220dd24fe4f86248eea3b1238a1fb60', + indexName: 'ohif', + }, + navbar: { + hideOnScroll: false, + logo: { + alt: 'OHIF Logo', + src: 'img/ohif-logo-light.svg', + srcDark: 'img/ohif-logo.svg', }, - ], - }, - footer: { - style: 'dark', - links: [ - { - title: ' ', - items: [ - { - // This doesn't show up on dev for some reason, but displays in build - html: ` + items: [ + { + to: 'https://ohif.org/get-started', + label: 'Get Started', + target: '_self', + position: 'left', + }, + { + to: 'https://ohif.org/examples', + label: 'Examples', + target: '_self', + position: 'left', + }, + { + position: 'left', + to: '/', + activeBaseRegex: '^(/next/|/)$', + docId: 'Introduction', + label: 'Docs', + }, + { + to: 'https://ohif.org/community', + label: 'Community', + target: '_self', + position: 'left', + }, + { + to: '/help', + //activeBaseRegex: '(^/help$)|(/help)', + label: 'Help', + position: 'right', + }, + { + type: 'docsVersionDropdown', + position: 'right', + dropdownActiveClassDisabled: true, + dropdownItemsAfter: [ + { + type: 'html', + value: '', + }, + { + type: 'html', + className: 'dropdown-archived-versions', + value: 'Archived versions', + }, + ...ArchivedVersionsDropdownItems.map( + ([versionName, versionUrl]) => ({ + label: versionName, + href: versionUrl, + }) + ), + { + type: 'html', + value: '', + }, + { + to: '/versions', + label: 'All versions', + }, + ], + }, + { + type: 'localeDropdown', + position: 'right', + dropdownItemsAfter: [ + { + to: '/platform/internationalization', + label: 'Help Us Translate', + }, + ], + }, + { + to: 'https://github.com/OHIF/Viewers', + position: 'right', + className: 'header-github-link', + 'aria-label': 'GitHub Repository', + }, + ], + }, + footer: { + style: 'dark', + links: [ + { + title: ' ', + items: [ + { + // This doesn't show up on dev for some reason, but displays in build + html: ` `, - }, - ], - }, - { - title: 'Learn', - items: [ - { - label: 'Introduction', - to: '/', - }, - { - label: 'Installation', - to: 'development/getting-started', - }, - ], - }, - { - title: 'Community', - items: [ - { - label: 'Discussion board', - to: 'https://community.ohif.org/', - }, - { - label: 'Help', - to: '/help', - }, - ], - }, - { - title: 'More', - items: [ - { - label: 'Donate', - to: 'https://giving.massgeneral.org/ohif', - }, - { - label: 'GitHub', - to: 'https://github.com/OHIF/Viewers', - }, - { - label: 'Twitter', - to: 'https://twitter.com/OHIFviewer', - }, - ], + }, + ], + }, + { + title: 'Learn', + items: [ + { + label: 'Introduction', + to: '/', + }, + { + label: 'Installation', + to: 'development/getting-started', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Discussion board', + to: 'https://community.ohif.org/', + }, + { + label: 'Help', + to: '/help', + }, + ], + }, + { + title: 'More', + items: [ + { + label: 'Donate', + to: 'https://giving.massgeneral.org/ohif', + }, + { + label: 'GitHub', + to: 'https://github.com/OHIF/Viewers', + }, + { + label: 'Twitter', + to: 'https://twitter.com/OHIFviewer', + }, + ], + }, + ], + logo: { + alt: 'OHIF ', + src: 'img/netlify-color-accent.svg', + href: 'https://v3-demo.ohif.org/', }, - ], - logo: { - alt: 'OHIF ', - src: 'img/netlify-color-accent.svg', - href: 'https://v3-demo.ohif.org/', + copyright: `OHIF is open source software released under the MIT license.`, }, - copyright: `OHIF is open source software released under the MIT license.`, - }, - }, -}); + }), +}; diff --git a/platform/docs/netlify.toml b/platform/docs/netlify.toml index 2e73378ea62..730eaab590f 100644 --- a/platform/docs/netlify.toml +++ b/platform/docs/netlify.toml @@ -17,7 +17,7 @@ # If 'production', `yarn install` does not install devDependencies NODE_ENV = "development" NODE_VERSION = "14.19.1" - YARN_VERSION = "1.22.0" + YARN_VERSION = "1.22.5" RUBY_VERSION = "2.6.2" YARN_FLAGS = "--no-ignore-optional --pure-lockfile" NETLIFY_USE_YARN = "true" diff --git a/platform/docs/package.json b/platform/docs/package.json index 0d6fdb33703..79f9bb7dde8 100644 --- a/platform/docs/package.json +++ b/platform/docs/package.json @@ -9,7 +9,8 @@ "@docusaurus/preset-classic", "@docusaurus/preset-classic/**", "**/react-router-dom", - "**/react-router" + "**/react-router", + "**/history" ] }, "scripts": { @@ -26,13 +27,14 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.3", - "@docusaurus/plugin-client-redirects": "2.0.0-beta.3", - "@docusaurus/plugin-ideal-image": "2.0.0-beta.3", - "@docusaurus/plugin-pwa": "2.0.0-beta.3", - "@docusaurus/preset-classic": "2.0.0-beta.3", - "@docusaurus/remark-plugin-npm2yarn": "2.0.0-beta.3", - "@docusaurus/theme-live-codeblock": "2.0.0-beta.3", + "@docusaurus/core": "2.0.0-beta.20", + "@docusaurus/plugin-client-redirects": "2.0.0-beta.20", + "@docusaurus/plugin-ideal-image": "2.0.0-beta.20", + "@docusaurus/plugin-pwa": "2.0.0-beta.20", + "@docusaurus/preset-classic": "2.0.0-beta.20", + "@docusaurus/remark-plugin-npm2yarn": "2.0.0-beta.20", + "@docusaurus/theme-live-codeblock": "2.0.0-beta.20", + "@docusaurus/plugin-google-gtag": "2.0.0-beta.20", "@mdx-js/react": "^1.6.21", "@svgr/webpack": "^5.5.0", "classnames": "^2.3.1", diff --git a/platform/docs/src/css/custom.css b/platform/docs/src/css/custom.css index a7517bbd556..2c59a3495b9 100644 --- a/platform/docs/src/css/custom.css +++ b/platform/docs/src/css/custom.css @@ -256,3 +256,20 @@ html[data-theme='dark'] .docusaurus-highlight-code-line { width: 300px; } } + +.dropdown-separator { + margin: 0.3rem 0; +} + +.dropdown-archived-versions { + font-size: 0.875rem; + padding: 0.2rem 0.5rem; +} + +.code-block-error-line { + background-color: #ff000020; + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); + border-left: 3px solid #ff000080; +} diff --git a/platform/docs/src/pages/versions.md b/platform/docs/src/pages/versions.md new file mode 100644 index 00000000000..f94dc5bd65e --- /dev/null +++ b/platform/docs/src/pages/versions.md @@ -0,0 +1,54 @@ +# Versions + +As we are increasing the efforts to make the OHIF platform more robust and up-to-date +with the latest software engineering practices, here we are listing the versions of +the OHIF platform that we are currently supporting, and the versions that have been +deprecated. + +## Product Version vs Package Version + +There are two types of versions that we need to consider here: + +- Product Version: which is the end user visible version of the viewer +- Package Version: which is the underlying libraries/packages of the platform on npm. + +Currently we have three product versions: + +- Version 1 (deprecated): Built with Meteor as a full stack application. +- Version 2 (master branch): Front end image viewer built with React +- Version 3 (alpha release): Re-architected Version 2.0 to allow for a more modular and customizable viewer. + +As per package versioning, we follow semantic versioning which looks like *a.b.c* where: + +- *a* is for major breaking changes +- *b* is for new features +- *c* is for patches/bug fixes + +You can read more semantic versioning [here](https://semver.org/). + + +## Maintained Product Versions + +### Version 3.1 Cornerstone3D + +OHIF Version 3.1 is the next major upcoming release of the OHIF platform. It uses +the next generation of the cornerstone library, [Cornerstone 3D](https://github.com/cornerstonejs/cornerstone3D-beta). +We are in the process of performing a parity check between this version and the `master` +branch before we merge it into the master branch. You can read more about the +roadmap timelines [here](https://ohif.org/roadmap/). + +### Version 2.0: Master branch + +Currently, the master branch of OHIF is on Product Version 2.0. + +## Archived Versions + +### Version 3.0 Cornerstone Legacy + +Version 3.0 which uses [cornerstone-core](https://github.com/cornerstonejs/cornerstone) and +[cornerstone-tools](https://github.com/cornerstonejs/cornerstoneTools) for rendering and +manipulation/annotation of images. + +### Version 1.0 Meteor + +Deprecated in favor of Version 2.0. Built using full stack Meteor as a full stack application. diff --git a/platform/docs/src/theme/DocVersionBanner/index.js b/platform/docs/src/theme/DocVersionBanner/index.js deleted file mode 100644 index b2a57d6551b..00000000000 --- a/platform/docs/src/theme/DocVersionBanner/index.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -import React from 'react'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Link from '@docusaurus/Link'; -import Translate from '@docusaurus/Translate'; -import { useActivePlugin, useDocVersionSuggestions } from '@theme/hooks/useDocs'; -import { useDocsPreferredVersion } from '@docusaurus/theme-common'; - -function UnreleasedVersionLabel({ siteTitle, versionMetadata }) { - return ( - {versionMetadata.label}, - latestVersionLink: ( - - - - docs - - - - ), - }}> - { - 'This is unreleased documentation for {siteTitle} {versionLabel} version. For up-to-date documentation, see OHIF v2.0 {latestVersionLink}.' - } - - ); -} - -function UnmaintainedVersionLabel({ siteTitle, versionMetadata }) { - return ( - {versionMetadata.label}, - }}> - { - 'This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.' - } - - ); -} - -const BannerLabelComponents = { - unreleased: UnreleasedVersionLabel, - unmaintained: UnmaintainedVersionLabel, -}; - -function BannerLabel(props) { - const BannerLabelComponent = - BannerLabelComponents[props.versionMetadata.banner]; - return ; -} - -function LatestVersionSuggestionLabel({ versionLabel, to, onClick }) { - return ( - - - - latest version - - - - ), - }}> - { - 'For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).' - } - - ); -} - -function DocVersionBannerEnabled({ versionMetadata }) { - const { - siteConfig: { title: siteTitle }, - } = useDocusaurusContext(); - const { pluginId } = useActivePlugin({ - failfast: true, - }); - - const getVersionMainDoc = (version) => - version.docs.find((doc) => doc.id === version.mainDocId); - - const { savePreferredVersionName } = useDocsPreferredVersion(pluginId); - const { - latestDocSuggestion, - latestVersionSuggestion, - } = useDocVersionSuggestions(pluginId); // try to link to same doc in latest version (not always possible) - // fallback to main doc of latest version - - const latestVersionSuggestedDoc = - latestDocSuggestion ?? getVersionMainDoc(latestVersionSuggestion); - return ( -
-
- -
- {/*
- savePreferredVersionName(latestVersionSuggestion.name)} - /> -
*/} -
- ); -} - -function DocVersionBanner({ versionMetadata }) { - if (versionMetadata.banner === 'none') { - return <>; - } else { - return ; - } -} - -export default DocVersionBanner; diff --git a/platform/docs/tailwind.config.js b/platform/docs/tailwind.config.js index e353fdd8e1a..d64678ef219 100644 --- a/platform/docs/tailwind.config.js +++ b/platform/docs/tailwind.config.js @@ -7,8 +7,8 @@ module.exports = { content: [ './docs/**/*.jsx', './docs/**/*.mdx', - './node_modules/@ohif/ui/src/**/*.{js,jsx,css}', - '../../node_modules/@ohif/ui/src/**/*.{js,jsx,css}', + './node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}', + '../../node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}', ], }, theme: { diff --git a/platform/docs/versioned_docs/version-2.0/viewer/configuration.md b/platform/docs/versioned_docs/version-2.0/viewer/configuration.md index 443e9c477c8..11b3aa7deff 100644 --- a/platform/docs/versioned_docs/version-2.0/viewer/configuration.md +++ b/platform/docs/versioned_docs/version-2.0/viewer/configuration.md @@ -98,12 +98,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/docs/versioned_docs/version-3.0/README.md b/platform/docs/versioned_docs/version-3.0/README.md new file mode 100644 index 00000000000..cd6cfe17bb5 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/README.md @@ -0,0 +1,97 @@ +--- +id: Introduction +slug: / +sidebar_position: 1 +--- + +The [Open Health Imaging Foundation][ohif-org] (OHIF) Viewer is an open source, +web-based, medical imaging platform. It aims to provide a core framework for +building complex imaging applications. + +Key features: + +- Designed to load large radiology studies as quickly as possible. Retrieves + metadata ahead of time and streams in imaging pixel data as needed. +- Leverages [Cornerstone.js](https://cornerstonejs.org/) for decoding, + rendering, and annotating medical images. +- Works out-of-the-box with Image Archives that support [DICOMWeb][dicom-web]. + Offers a Data Source API for communicating with archives over proprietary API + formats. +- Provides a plugin framework for creating task-based workflow modes which can + re-use core functionality. +- Beautiful user interface (UI) designed with extensibility in mind. UI + components available in a reusable component library built with React.js and + Tailwind CSS + +![OHIF Viewer Screenshot](./assets/img/OHIF-Viewer.png) + + + +## Where to next? + +The Open Health Imaging Foundation intends to provide an imaging viewer +framework which can be easily extended for specific uses. If you find yourself +unable to extend the viewer for your purposes, please reach out via our [GitHub +issues][gh-issues]. We are actively seeking feedback on ways to improve our +integration and extension points. + +Check out these helpful links: + +- Ready to dive into some code? Check out our + [Getting Started Guide](./development/getting-started.md). +- We're an active, vibrant community. + [Learn how you can be more involved.](./development/contributing.md) +- Feeling lost? Read our [help page](/help). + +## Citing OHIF + +To cite the OHIF Viewer in an academic publication, please cite + +> _Open Health Imaging Foundation Viewer: An Extensible Open-Source Framework +> for Building Web-Based Imaging Applications to Support Cancer Research_ +> +> Erik Ziegler, Trinity Urban, Danny Brown, James Petts, Steve D. Pieper, Rob +> Lewis, Chris Hafey, and Gordon J. Harris _JCO Clinical Cancer Informatics_, no. 4 (2020), 336-345, DOI: +> [10.1200/CCI.19.00131](https://www.doi.org/10.1200/CCI.19.00131) + +This article is freely available on Pubmed Central: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7259879/ + + +or, for Lesion Tracker of OHIF v1, please cite: + +> _LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer +> Imaging Research and Clinical Trials_ +> +> Trinity Urban, Erik Ziegler, Rob Lewis, Chris Hafey, Cheryl Sadow, Annick D. +> Van den Abbeele and Gordon J. Harris _Cancer Research_, November 1 2017 (77) (21) e119-e122 DOI: +> [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) + +This article is freely available on Pubmed Central. +https://pubmed.ncbi.nlm.nih.gov/29092955/ + + +**Note:** If you use or find this repository helpful, please take the time to +star this repository on Github. This is an easy way for us to assess adoption, +and it can help us obtain future funding for the project. + +## License + +MIT © [OHIF](https://github.com/OHIF) + +  + + + + +[ohif-org]: https://www.ohif.org +[ohif-demo]: http://v3-demo.ohif.org/ +[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb +[gh-issues]: https://github.com/OHIF/Viewers/issues + diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram b/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram new file mode 100644 index 00000000000..bbf6cf58b7a Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/designs/architecture-diagram differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig new file mode 100644 index 00000000000..8756e9f7951 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/designs/canny-full.fig differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg b/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg new file mode 100644 index 00000000000..ad04389c61a --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/assets/designs/cloud.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram b/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram new file mode 100644 index 00000000000..182ad232399 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/designs/embedded-viewer-diagram differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig new file mode 100644 index 00000000000..460ae95dd35 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/designs/nginx-image-archive.fig differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg b/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg new file mode 100644 index 00000000000..8e4aac5d237 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/assets/designs/npm-logo-red.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig new file mode 100644 index 00000000000..5eb82e561df Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/designs/scope-of-project.fig differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig b/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig new file mode 100644 index 00000000000..8982a8fedd5 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/designs/user-access-control-request-flow.fig differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png new file mode 100644 index 00000000000..7f28ceb4f30 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-Viewer.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png new file mode 100644 index 00000000000..4a58a1826a4 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/OHIF-e2e-test-studies.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png b/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png new file mode 100644 index 00000000000..fc477adf66d Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/SR-exported.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png new file mode 100644 index 00000000000..3e562a7971d Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_DEPLOY.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png new file mode 100644 index 00000000000..f9c4a568b6c Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_CHECKS.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png new file mode 100644 index 00000000000..54b0aa39fbb Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png new file mode 100644 index 00000000000..f3c2a806973 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/WORKFLOW_RELEASE.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png b/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png new file mode 100644 index 00000000000..bb4955e97fb Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/add-extension.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png new file mode 100644 index 00000000000..6f1a16280db Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/add-mode.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png new file mode 100644 index 00000000000..40b511309b8 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-no-verbose.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png new file mode 100644 index 00000000000..b15713b3985 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/cli-search-with-verbose.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png new file mode 100644 index 00000000000..68ea6dc01a7 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png new file mode 100644 index 00000000000..7b0375dcc39 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/clock-mode1.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif b/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif new file mode 100644 index 00000000000..22fde7a7f10 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/cornerstone-tools-link.gif differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png b/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png new file mode 100644 index 00000000000..a68264936a8 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/create-extension.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png b/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png new file mode 100644 index 00000000000..3ca4e26b006 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/create-mode.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png b/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png new file mode 100644 index 00000000000..ea3c9ac9794 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/custom-logo.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png new file mode 100644 index 00000000000..2d77dafe7b5 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json-public.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png new file mode 100644 index 00000000000..8eed743e2ed Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/dicom-json.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png b/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png new file mode 100644 index 00000000000..ad33ebe8865 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/docker-pacs.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png new file mode 100644 index 00000000000..49b3a4173f3 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress-final.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png new file mode 100644 index 00000000000..89ccc3e44af Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/e2e-cypress.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png b/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png new file mode 100644 index 00000000000..426cb7ab855 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/embedded-viewer-diagram.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png b/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png new file mode 100644 index 00000000000..f26509a16fa Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/jwt-explained.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png new file mode 100644 index 00000000000..0ea77f9655f Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-default-theme.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png new file mode 100644 index 00000000000..ad060f262a6 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/keycloak-ohif-theme.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg b/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg new file mode 100644 index 00000000000..1139aa2c760 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/assets/img/locizeSponsor.svg @@ -0,0 +1,187 @@ + + + + Custom Preset 2 Copy + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png b/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png new file mode 100644 index 00000000000..3c6ba7d5316 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/locked-sr.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png new file mode 100644 index 00000000000..cafeae72cb2 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-1.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png new file mode 100644 index 00000000000..12d21a0a7ab Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-prompt.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png new file mode 100644 index 00000000000..90758691b08 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-panel-tracked.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png new file mode 100644 index 00000000000..9d46fd3192d Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/measurement-temporary.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png b/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png new file mode 100644 index 00000000000..d4bd71bb6c1 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/measurements-prevNext.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png new file mode 100644 index 00000000000..f931818c374 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/mode-archs.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png new file mode 100644 index 00000000000..d855edbca75 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/mode-clock.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png b/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png new file mode 100644 index 00000000000..9442411b5e0 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/mode-template.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png b/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png new file mode 100644 index 00000000000..bd75479652a Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/nginx-image-archive.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png b/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png new file mode 100644 index 00000000000..a992f0169b2 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/ohif-cli-list.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png b/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png new file mode 100644 index 00000000000..5b881abdfd8 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/open-graph.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/overview.png b/platform/docs/versioned_docs/version-3.0/assets/img/overview.png new file mode 100644 index 00000000000..d504f5b141e Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/overview.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png new file mode 100644 index 00000000000..fdbb5588489 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-left-right.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png new file mode 100644 index 00000000000..83f9e19198a Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/panel-module-v3.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png b/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png new file mode 100644 index 00000000000..b1e4c535131 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/panelmodule-icon.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png b/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png new file mode 100644 index 00000000000..7aeca269257 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/restore-exported-sr.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png b/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png new file mode 100644 index 00000000000..6daac8bee38 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/scope-of-project.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png new file mode 100644 index 00000000000..e5251edd2d9 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/services-data.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png new file mode 100644 index 00000000000..900419a8d1a Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/services-measurements.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png b/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png new file mode 100644 index 00000000000..34c3bf1ffe2 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/services-ui.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/services.png b/platform/docs/versioned_docs/version-3.0/assets/img/services.png new file mode 100644 index 00000000000..569c046c0df Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/services.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif b/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif new file mode 100644 index 00000000000..545f0686358 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/surge-deploy.gif differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png new file mode 100644 index 00000000000..465c2a91e36 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/template-extension-files.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png new file mode 100644 index 00000000000..0c44ca962d1 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-files.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png new file mode 100644 index 00000000000..c63d1722ba5 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/template-mode-ui.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png new file mode 100644 index 00000000000..57536695f63 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/toolbar-module.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png new file mode 100644 index 00000000000..8190b5f96e4 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-layout.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png new file mode 100644 index 00000000000..1a85837f38d Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-nested-buttons.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png new file mode 100644 index 00000000000..00acfcaec2a Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/toolbarModule-zoom.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png new file mode 100644 index 00000000000..d61b36d7e87 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/tracked-not-tracked.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png new file mode 100644 index 00000000000..d5b5959fa2e Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow1.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png new file mode 100644 index 00000000000..988e56d4b03 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow2.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png new file mode 100644 index 00000000000..c62fde75cf5 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/tracking-workflow3.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif b/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif new file mode 100644 index 00000000000..599964e2444 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/ui-modal.gif differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png b/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png new file mode 100644 index 00000000000..dd530637775 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/ui-services.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png new file mode 100644 index 00000000000..573c835038e Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-access-control-request-flow.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png new file mode 100644 index 00000000000..7621c4fedc7 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys-default.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png new file mode 100644 index 00000000000..389aa31dd16 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-hotkeys.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png new file mode 100644 index 00000000000..1ce61742d97 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-measurement-export.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png new file mode 100644 index 00000000000..5e2b29c4ed1 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-open-viewer.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png new file mode 100644 index 00000000000..05d0c4ba7d8 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-filter.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png new file mode 100644 index 00000000000..4d589599ef8 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-list.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png new file mode 100644 index 00000000000..b082eedea24 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-next.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png new file mode 100644 index 00000000000..42db7c2f780 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-panel.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png new file mode 100644 index 00000000000..e5a5aad16af Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-study-summary.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png new file mode 100644 index 00000000000..bf878bc5919 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-studyist-modespecific.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png new file mode 100644 index 00000000000..ca7eef55c15 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-download-icon.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png new file mode 100644 index 00000000000..15632fd0a21 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-extra.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png new file mode 100644 index 00000000000..5a240146868 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbar-preset.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png new file mode 100644 index 00000000000..d39c9cbe071 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-toolbarDownload.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png new file mode 100644 index 00000000000..71110885db7 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-layout.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png new file mode 100644 index 00000000000..1ef3e76da20 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-main.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png new file mode 100644 index 00000000000..2cb7fd7f0af Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar-measurements.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png new file mode 100644 index 00000000000..fc36c58c0c1 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer-toolbar.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png new file mode 100644 index 00000000000..c79dce13ddd Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/user-viewer.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png new file mode 100644 index 00000000000..58af0ad27e2 Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule-layout.png differ diff --git a/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png new file mode 100644 index 00000000000..05423370c7a Binary files /dev/null and b/platform/docs/versioned_docs/version-3.0/assets/img/viewportModule.png differ diff --git a/platform/docs/versioned_docs/version-3.0/configuration/_category_.json b/platform/docs/versioned_docs/version-3.0/configuration/_category_.json new file mode 100644 index 00000000000..0aea1748dc4 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/configuration/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Configuration", + "position": 4 +} diff --git a/platform/docs/docs/configuration/configuration.md b/platform/docs/versioned_docs/version-3.0/configuration/configuration.md similarity index 100% rename from platform/docs/docs/configuration/configuration.md rename to platform/docs/versioned_docs/version-3.0/configuration/configuration.md diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json new file mode 100644 index 00000000000..fe1e3e8595e --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Data Sources", + "position": 2 +} diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md new file mode 100644 index 00000000000..c6efab62ed8 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-json.md @@ -0,0 +1,155 @@ +--- +sidebar_position: 3 +sidebar_label: DICOM JSON +--- + +# DICOM JSON + +You can launch the OHIF Viewer with a JSON file which points to a DICOMWeb +server as well as a list of study and series instance UIDs along with metadata. + +An example would look like + +`https://v3-demo.ohif.org/viewer/dicomjson?url=https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001.json` + +As you can see the url to the location of the JSON file is passed in the query +after the `dicomjson` string, which is +`https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001.json` (this +json file has been generated by OHIF team and stored in an amazon s3 bucket for +the purpose of the guide). + +## DICOM JSON sample + +Here we are using the LIDC-IDRI-0001 case which is a sample of the LIDC-IDRI +dataset. Let's have a look at the JSON file: + +### Metadata + +JSON file stores the metadata for the study level, series level and instance +level. A JSON launch file should follow the same structure as the one below. + +Note that at the instance level metadata we are storing both the `metadata` and +also the `url` for the dicom file on the dicom server. In this case we are +referring to +`dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm` +which is stored in another directory in our s3. (You can actually try +downloading the dicom file by opening the url in your browser). + +```json +{ + "studies": [ + // first study metadata + { + "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", + "StudyDate": "20000101", + "StudyTime": "", + "PatientName": "", + "PatientID": "LIDC-IDRI-0001", + "AccessionNumber": "", + "PatientAge": "", + "PatientSex": "", + "series": [ + // first series metadata + { + "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", + "SeriesNumber": 3000566, + "Modality": "CT", + "SliceThickness": 2.5, + "instances": [ + // first instance metadata + { + "metadata": { + "Columns": 512, + "Rows": 512, + "InstanceNumber": 1, + "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2", + "PhotometricInterpretation": "MONOCHROME2", + "BitsAllocated": 16, + "BitsStored": 16, + "PixelRepresentation": 1, + "SamplesPerPixel": 1, + "PixelSpacing": [0.703125, 0.703125], + "HighBit": 15, + "ImageOrientationPatient": [1, 0, 0, 0, 1, 0], + "ImagePositionPatient": [-166, -171.699997, -10], + "FrameOfReferenceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.229925374658226729607867499499", + "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"], + "Modality": "CT", + "SOPInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.262721256650280657946440242654", + "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", + "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", + "WindowCenter": -600, + "WindowWidth": 1600, + "SeriesDate": "20000101" + }, + "url": "dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm" + }, + // second instance metadata + { + "metadata": { + "Columns": 512, + "Rows": 512, + "InstanceNumber": 2, + "SOPClassUID": "1.2.840.10008.5.1.4.1.1.2", + "PhotometricInterpretation": "MONOCHROME2", + "BitsAllocated": 16, + "BitsStored": 16, + "PixelRepresentation": 1, + "SamplesPerPixel": 1, + "PixelSpacing": [0.703125, 0.703125], + "HighBit": 15, + "ImageOrientationPatient": [1, 0, 0, 0, 1, 0], + "ImagePositionPatient": [-166, -171.699997, -12.5], + "FrameOfReferenceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.229925374658226729607867499499", + "ImageType": ["ORIGINAL", "PRIMARY", "AXIAL"], + "Modality": "CT", + "SOPInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.512235483218154065970649917292", + "SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192", + "StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178", + "WindowCenter": -600, + "WindowWidth": 1600, + "SeriesDate": "20000101" + }, + "url": "dicomweb:https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm" + } + // ..... other instances metadata + ] + } + // ... other series metadata + ], + "NumInstances": 133, + "Modalities": "CT" + } + // second study metadata + ] +} +``` + +![](../../assets/img/dicom-json.png) + +### Local Demo + +You can run OHIF with a JSON data source against you local datasets (given that +their JSON metadata is extracted). + +First you need to put the JSON file and the folder containing the dicom files +inside your `public` folder. Since files are served from your local server the +`url` for the JSON file will be `http://localhost:3000/LIDC-IDRI-0001.json` and +the dicom files will be +`dicomweb:http://localhost:3000/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm`. + +After `yarn install` and running `yarn dev` and opening the browser at +`http://localhost:3000/viewer/dicomjson?url=http://localhost:3000/LIDC-IDRI-0001.json` +will display the viewer. + +Download JSON file from +[here](https://www.dropbox.com/sh/zvkv6mrhpdze67x/AADLGK46WuforD2LopP99gFXa?dl=0) + +Sample DICOM files can be downloaded from +[TCIA](https://wiki.cancerimagingarchive.net/display/Public/LIDC-IDRI) or +directly from +[here](https://www.dropbox.com/sh/zvkv6mrhpdze67x/AADLGK46WuforD2LopP99gFXa?dl=0) + +Your public folder should look like this: + +![](../../assets/img/dicom-json-public.png) diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md new file mode 100644 index 00000000000..83fbc1750bf --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/dicom-web.md @@ -0,0 +1,191 @@ +--- +sidebar_position: 1 +sidebar_label: DICOMweb +--- + +# DICOMweb + +## Set up a local DICOM server + +ATTENTION! Already have a remote or local server? Skip to the +[configuration section](#configuration-learn-more) below. + +While the OHIF Viewer can work with any data source, the easiest to configure +are the ones that follow the [DICOMWeb][dicom-web] spec. + +1. Choose and install an Image Archive +2. Upload data to your archive (e.g. with DCMTK's [storescu][storescu] or your + archive's web interface) +3. Keep the server running + +For our purposes, we will be using `Orthanc`, but you can see a list of +[other Open Source options](#open-source-dicom-image-archives) below. + +### Requirements + +- Docker + - [Docker for Mac](https://docs.docker.com/docker-for-mac/) + - [Docker for Windows (recommended)](https://docs.docker.com/docker-for-windows/) + - [Docker Toolbox for Windows](https://docs.docker.com/toolbox/toolbox_install_windows/) + +_Not sure if you have `docker` installed already? Try running `docker --version` +in command prompt or terminal_ + +> If you are using `Docker Toolbox` you need to change the _PROXY_DOMAIN_ +> parameter in _platform/viewer/package.json_ to http://192.168.99.100:8042 or +> the ip docker-machine ip throws. This is the value [`WebPack`][webpack-proxy] +> uses to proxy requests + +## Open Source DICOM Image Archives + +There are a lot of options available to you to use as a local DICOM server. Here +are some of the more popular ones: + +| Archive | Installation | +| --------------------------------------------- | ---------------------------------- | +| [DCM4CHEE Archive 5.x][dcm4chee] | [W/ Docker][dcm4chee-docker] | +| [Orthanc][orthanc] | [W/ Docker][orthanc-docker] | +| [DICOMcloud][dicomcloud] (**DICOM Web only**) | [Installation][dicomcloud-install] | +| [OsiriX][osirix] (**Mac OSX only**) | Desktop Client | +| [Horos][horos] (**Mac OSX only**) | Desktop Client | + +_Feel free to make a Pull Request if you want to add to this list._ + +Below, we will focus on `DCM4CHEE` and `Orthanc` usage: + +### Running Orthanc + +_Start Orthanc:_ + +```bash +# Runs orthanc so long as window remains open +yarn run orthanc:up +``` + +_Upload your first Study:_ + +1. Navigate to + [Orthanc's web interface](http://localhost:8042/app/explorer.html) at + `http://localhost:8042/app/explorer.html` in a web browser. +2. In the top right corner, click "Upload" +3. Click "Select files to upload..." and select one or more DICOM files +4. Click "Start the upload" + +#### Orthanc: Learn More + +You can see the `docker-compose.yml` file this command runs at +[`/.docker/Nginx-Orthanc/`][orthanc-docker-compose], and more on +Orthanc for Docker in [Orthanc's documentation][orthanc-docker]. + +#### Connecting to Orthanc + +Now that we have a local Orthanc instance up and running, we need to configure +our web application to connect to it. Open a new terminal window, navigate to +this repository's root directory, and run: + +```bash +# If you haven't already, enable yarn workspaces +yarn config set workspaces-experimental true + +# Restore dependencies +yarn install + +# Run our dev command, but with the local orthanc config +yarn run dev:orthanc +``` + +#### Configuration: Learn More + +> For more configuration fun, check out the +> [Essentials Configuration](../index.md) guide. + +Let's take a look at what's going on under the hood here. `yarn run dev:orthanc` +is running the `dev:orthanc` script in our project's `package.json` (inside +`platform/viewer`). That script is: + +```js +cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w +``` + +- `cross-env` sets three environment variables + - PROXY_TARGET: `/dicom-web` + - PROXY_DOMAIN: `http://localhost:8042` + - APP_CONFIG: `config/docker_nginx-orthanc.js` +- `webpack-dev-server` runs using the `.webpack/webpack.pwa.js` configuration + file. It will watch for changes and update as we develop. + +`PROXY_TARGET` and `PROXY_DOMAIN` tell our development server to proxy requests +to `Orthanc`. This allows us to bypass CORS issues that normally occur when +requesting resources that live at a different domain. + +The `APP_CONFIG` value tells our app which file to load on to `window.config`. +By default, our app uses the file at +`/platform/viewer/public/config/default.js`. Here is what that +configuration looks like: + +```js +window.config = { + routerBasename: '/', + extensions: [], + modes: [], + showStudyList: true, + dataSources: [ + { + friendlyName: 'dcmjs DICOMWeb Server', + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + name: 'DCM4CHEE', + wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + qidoSupportsIncludeField: true, + supportsReject: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: true, + }, + }, + ], + defaultDataSourceName: 'dicomweb', +}; +``` + +To learn more about how you can configure the OHIF Viewer, check out our +[Configuration Guide](../index.md). + +### Running DCM4CHEE + +dcm4che is a collection of open source applications for healthcare enterprise +written in Java programming language which implements DICOM standard. dcm4chee +(extra 'e' at the end) is dcm4che project for an Image Manager/Image Archive +which provides storage, retrieval and other functionalities. You can read more +about dcm4chee in their website [here](https://www.dcm4che.org/) + +DCM4chee installation is out of scope for these tutorials and can be found +[here](https://github.com/dcm4che/dcm4chee-arc-light/wiki/Run-minimum-set-of-archive-services-on-a-single-host) + +An overview of steps for running OHIF Viewer using a local DCM4CHEE is shown +below: + +
+ +
+ +[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light +[dcm4chee-docker]: + https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker +[orthanc]: https://www.orthanc-server.com/ +[orthanc-docker]: http://book.orthanc-server.com/users/docker.html +[dicomcloud]: https://github.com/DICOMcloud/DICOMcloud +[dicomcloud-install]: https://github.com/DICOMcloud/DICOMcloud#running-the-code +[osirix]: http://www.osirix-viewer.com/ +[horos]: https://www.horosproject.org/ +[default-config]: + https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/default.js +[html-templates]: + https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates +[config-files]: + https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config diff --git a/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md new file mode 100644 index 00000000000..62c53d69b93 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/configuration/dataSources/static-files.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 2 +sidebar_label: Static Files +--- + +# Static Files + +There is a binary DICOM to static file generator, which provides easily served +binary files. The files are all compressed in order to reduce space +significantly, and are pre-computed for the files required for OHIF, so that the +performance of serving the files is just the read from disk/write to http stream +time, without any extra processing time. + +The project for the static wado files is located here: [static-wado]: +https://github.com/wayfarer3130/static-wado + +It can be compiled with Java and Gradle, and then run against a set of dicom, in +the example located in /dicom/study1 outputting to /dicomweb, and then a server +run against that data, like this: + +``` +git clone https://github.com/wayfarer3130/static-wado.git +cd static-wado +./gradlew installDist +StaticWado/build/install/StaticWado/bin/StaticWado -d /dicomweb /dicom/study1 +cd /dicomweb +npx http-server -p 5000 --cors -g +``` + +There is then a dev environment in the platform/viewer directory which can be +run against those files, like this: + +``` +cd platform/viewer +yarn dev:static +``` + +Additional studies can be added to the dicomweb by re-running the StaticWado +command. It will create a single studies.gz index file (JSON DICOM file, +compressed) containing an index of all studies created. There is then a small +extension to OHIF which performs client side indexing. + +The StaticWado command also knows how to deploy a client and dicomweb directory +to Amazon s3, which can then server files up directly. There is another build +setup build:aws in the viewer package.json to create such a deployment. diff --git a/platform/docs/versioned_docs/version-3.0/configuration/index.md b/platform/docs/versioned_docs/version-3.0/configuration/index.md new file mode 100644 index 00000000000..f51abd3d3ac --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/configuration/index.md @@ -0,0 +1,168 @@ +--- +sidebar_position: 1 +sidebar_label: Overview +--- + +# Overview + +After following the steps outlined in +[Getting Started](./../development/getting-started.md), you'll notice that the +OHIF Viewer has data for several studies and their images. You didn't add this +data, so where is it coming from? + +By default, the viewer is configured to connect to a remote server hosted by the +nice folks over at [dcmjs.org][dcmjs-org]. While convenient for getting started, +the time may come when you want to develop using your own data either locally or +remotely. + +## Configuration Files + +The configuration for our viewer is in the `platform/viewer/public/config` +directory. Our build process knows which configuration file to use based on the +`APP_CONFIG` environment variable. By default, its value is +[`config/default.js`][default-config]. The majority of the viewer's features, +and registered extension's features, are configured using this file. + +The simplest way is to update the existing default config: + +```js title="platform/viewer/public/config/default.js" +window.config = { + routerBasename: '/', + extensions: [], + modes: [], + showStudyList: true, + dataSources: [ + { + friendlyName: 'dcmjs DICOMWeb Server', + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + name: 'DCM4CHEE', + wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + qidoSupportsIncludeField: true, + supportsReject: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: true, + }, + }, + ], + defaultDataSourceName: 'dicomweb', +}; +``` + +> As you can see a new change in `OHIF-v3` is the addition of `dataSources`. You +> can build your own datasource and map it to the internal data structure of +> OHIF’s > metadata and enjoy using other peoples developed mode on your own +> data! +> +> You can read more about data sources at +> [Data Source section in Modes](../platform/modes/index.md) + +The configuration can also be written as a JS Function in case you need to +inject dependencies like external services: + +```js +window.config = ({ servicesManager } = {}) => { + const { UIDialogService } = servicesManager.services; + return { + cornerstoneExtensionConfig: { + tools: { + ArrowAnnotate: { + configuration: { + getTextCallback: (callback, eventDetails) => UIDialogService.create({... + } + } + }, + }, + routerBasename: '/', + dataSources: [ + { + friendlyName: 'dcmjs DICOMWeb Server', + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + name: 'DCM4CHEE', + wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado', + qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs', + qidoSupportsIncludeField: true, + supportsReject: true, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: true, + supportsWildcard: true, + }, + }, + ], + defaultDataSourceName: 'dicomweb', + }; +}; +``` + + + +## Environment Variables + +We use environment variables at build and dev time to change the Viewer's +behavior. We can update the `HTML_TEMPLATE` to easily change which extensions +are registered, and specify a different `APP_CONFIG` to connect to an +alternative data source (or even specify different default hotkeys). + +| Environment Variable | Description | Default | +| -------------------- | -------------------------------------------------------------------------------------------------- | ------------------- | +| `HTML_TEMPLATE` | Which [HTML template][html-templates] to use as our web app's entry point. Specific to PWA builds. | `index.html` | +| `PUBLIC_URL` | The route relative to the host that the app will be served from. Specific to PWA builds. | `/` | +| `APP_CONFIG` | Which [configuration file][config-file] to copy to output as `app-config.js` | `config/default.js` | +| `PROXY_TARGET` | When developing, proxy requests that match this pattern to `PROXY_DOMAIN` | `undefined` | +| `PROXY_DOMAIN` | When developing, proxy requests from `PROXY_TARGET` to `PROXY_DOMAIN` | `undefined` | + +You can also create a new config file and specify its path relative to the build +output's root by setting the `APP_CONFIG` environment variable. You can set the +value of this environment variable a few different ways: + +- ~[Add a temporary environment variable in your shell](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-temporary-environment-variables-in-your-shell)~ + - Previous `react-scripts` functionality that we need to duplicate with + `dotenv-webpack` +- ~[Add environment specific variables in `.env` file(s)](https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env)~ + - Previous `react-scripts` functionality that we need to duplicate with + `dotenv-webpack` +- Using the `cross-env` package in a npm script: + - `"build": "cross-env APP_CONFIG=config/my-config.js react-scripts build"` + +After updating the configuration, `yarn run build` to generate updated build +output. + + + + +[dcmjs-org]: https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado +[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb +[storescu]: https://support.dcmtk.org/docs/storescu.html +[webpack-proxy]: https://webpack.js.org/configuration/dev-server/#devserverproxy +[orthanc-docker-compose]: https://github.com/OHIF/Viewers/tree/master/.docker/Nginx-Orthanc + +[dcm4chee]: https://github.com/dcm4che/dcm4chee-arc-light +[dcm4chee-docker]: https://github.com/dcm4che/dcm4chee-arc-light/wiki/Running-on-Docker +[orthanc]: https://www.orthanc-server.com/ +[orthanc-docker]: https://book.orthanc-server.com/users/docker.html +[dicomcloud]: https://github.com/DICOMcloud/DICOMcloud +[dicomcloud-install]: https://github.com/DICOMcloud/DICOMcloud#running-the-code +[osirix]: https://www.osirix-viewer.com/ +[horos]: https://www.horosproject.org/ +[default-config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/public/config/default.js +[html-templates]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/html-templates +[config-files]: https://github.com/OHIF/Viewers/tree/master/platform/viewer/public/config + diff --git a/platform/docs/versioned_docs/version-3.0/deployment/_category_.json b/platform/docs/versioned_docs/version-3.0/deployment/_category_.json new file mode 100644 index 00000000000..534be1dfb6d --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/deployment/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Deployment", + "position": 3 +} diff --git a/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md b/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md new file mode 100644 index 00000000000..b23463237a4 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/deployment/build-for-production.md @@ -0,0 +1,132 @@ +--- +sidebar_position: 2 +--- + +# Build for Production + +### Build Machine Requirements + +- [Node.js & NPM](https://nodejs.org/en/download/) +- [Yarn](https://yarnpkg.com/lang/en/docs/install/) +- [Git](https://www.atlassian.com/git/tutorials/install-git) + +### Getting the Code + +_With Git:_ + +```bash +# Clone the remote repository to your local machine +git clone https://github.com/OHIF/Viewers.git +``` + +More on: _[`git clone`](https://git-scm.com/docs/git-clone), +[`git checkout`](https://git-scm.com/docs/git-checkout)_ + +_From .zip:_ + +[OHIF/Viewers: master.zip](https://github.com/OHIF/Viewers/archive/master.zip) + +### Restore Dependencies & Build + +Open your terminal, and navigate to the directory containing the source files. +Next run these commands: + +```bash +# If you haven't already, enable yarn workspaces +yarn config set workspaces-experimental true + +# Restore dependencies +yarn install + +# Build source code for production +yarn run build +``` + +If everything worked as expected, you should have a new `dist/` directory in the +`platform/viewer/dist` folder. It should roughly resemble the following: + +```bash title="platform/viewer/dist/" +├── app-config.js +├── app.bundle.js +├── app.css +├── index.html +├── manifest.json +├── service-worker.js +└── ... +``` + +By default, the build output will connect to OHIF's publicly accessible PACS. If +this is your first time setting up the OHIF Viewer, it is recommended that you +test with these default settings. After testing, you can find instructions on +how to configure the project for your own imaging archive below. + +### Configuration + +The configuration for our viewer is in the `platform/viewer/public/config` +directory. Our build process knows which configuration file to use based on the +`APP_CONFIG` environment variable. By default, its value is +[`config/default.js`][default-config]. The majority of the viewer's features, +and registered extension's features, are configured using this file. + +The easiest way to apply your own configuration is to modify the `default.js` +file. For more advanced configuration options, check out our +[configuration essentials guide](../configuration/index.md). + +## Next Steps + +### Deploying Build Output + +_Drag-n-drop_ + +- [Netlify: Drop](./static-assets#netlify-drop) + +_Easy_ + +- [Surge.sh](./static-assets#surgesh) +- [GitHub Pages](./static-assets#github-pages) + +_Advanced_ + +- [AWS S3 + Cloudfront](./static-assets#aws-s3--cloudfront) +- [GCP + Cloudflare](./static-assets#gcp--cloudflare) +- [Azure](./static-assets#azure) + +### Testing Build Output Locally + +A quick way to test your build output locally is to spin up a small webserver. +You can do this by running the following commands in the `dist/` output +directory: + +```bash +# Install http-server as a globally available package +yarn global add http-server + +# Change the directory to the platform/viewer + +# Serve the files in our current directory +# Accessible at: `http://localhost:8080` +npx http-server ./dist +``` + +
+ +
+ +### Automating Builds and Deployments + +If you found setting up your environment and running all of these steps to be a +bit tedious, then you are in good company. Thankfully, there are a large number +of tools available to assist with automating tasks like building and deploying +web application. For a starting point, check out this repository's own use of: + +- [CircleCI][circleci]: [config.yaml][circleci-config] +- [Netlify][netlify]: [netlify.toml][netlify.toml] | + [build-deploy-preview.sh][build-deploy-preview.sh] + + +[circleci]: https://circleci.com/gh/OHIF/Viewers +[circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml +[netlify]: https://app.netlify.com/sites/ohif/deploys +[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/netlify.toml +[build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh + diff --git a/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md b/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md new file mode 100644 index 00000000000..532bf4cc636 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/deployment/google-cloud-healthcare.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 6 +--- + +# Google Cloud Healthcare + +> Coming soon - We are working on bringing Google Cloud Healthcare to OHIF-v3 diff --git a/platform/docs/versioned_docs/version-3.0/deployment/index.md b/platform/docs/versioned_docs/version-3.0/deployment/index.md new file mode 100644 index 00000000000..384aefc777d --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/deployment/index.md @@ -0,0 +1,335 @@ +--- +sidebar_position: 1 +sidebar_label: Overview +--- + +# Deployment + +The OHIF Viewer can be embedded in other web applications via it's [packaged +script source][viewer-npm], or served up as a stand-alone PWA ([progressive web +application][pwa-url]) by building and hosting a collection of static assets. In +either case, you will need to configure your instance of the Viewer so that it +can connect to your data source (the database or PACS that provides the data +your Viewer will display). + +## Overview + +Our goal is to make deployment as simple and painless as possible; however, +there is an inherent amount of complexity in configuring and deploying web +applications. If you find yourself a little lost, please don't hesitate to +[reach out for help](/help) + +## Deployment Scenarios + +### Embedded Viewer (deprecated) + +`OHIF-v3` has deprecated deploying the viewer as an embedded viewer the number +of underlying libraries that run web workers are increasing for OHIF. An example +of these libraries is OHIF's 3D rendering functionality that is provided by +`vtk-js`. + +### Stand-alone Viewer + +Deploying the OHIF Viewer as a stand-alone web application provides many +benefits, but comes at the cost of time and complexity. Some benefits include: + +_Today:_ + +- Leverage [extensions](../platform/extensions/index.md) and + [modes](../platform/modes/index.md) to drop-in powerful new features +- Add routes and customize the viewer's workflow +- Finer control over styling and whitelabeling + +_In the future:_ + +- The ability to package the viewer for [App Store distribution][app-store] +- Leverage `service-workers` for offline support and speed benefits from caching + +#### Hosted Static Assets + +At the end of the day, a production OHIF Viewer instance is a collection of +HTML, CSS, JS, Font Files, and Images. We "build" those files from our +`source code` with configuration specific to our project. We then make those +files publicly accessible by hosting them on a Web Server. + +If you have not deployed a web application before, this may be a good time to +[reach out for help](/help), as these steps assume prior web development and +deployment experience. + +##### Part 1 - Build Production Assets + +"Building", or creating, the files you will need is the same regardless of the +web host you choose. You can find detailed instructions on how to configure and +build the OHIF Viewer in our +["Build for Production" guide](./build-for-production.md). + +##### Part 2 - Host Your App + +There are a lot of [benefits to hosting static assets][host-static-assets] over +dynamic content. You can find instructions on how to host your build's output +via one of these guides: + +_Drag-n-drop_ + +- [Netlify: Drop](./static-assets.md#netlify-drop) + +_Easy_ + +- [Surge.sh](./static-assets.md#surgesh) +- [GitHub Pages](./static-assets.md#github-pages) + +_Advanced_ + +- [AWS S3 + Cloudfront](./static-assets.md#aws-s3--cloudfront) +- [GCP + Cloudflare](./static-assets.md#gcp--cloudflare) +- [Azure](./static-assets.md#azure) + +## Data + +The OHIF Viewer is able to connect to any data source that implements the [DICOM +Web Standard][dicom-web-standard]. [DICOM Web][dicom-web] refers to RESTful +DICOM Services -- a recently standardized set of guidelines for exchanging +medical images and imaging metadata over the internet. Not all archives fully +support it yet, but it is gaining wider adoption. + +### Configure Connection + +If you have an existing archive and intend to host the OHIF Viewer at the same +domain name as your archive, then connecting the two is as simple as following +the steps layed out in our +[Configuration Essentials Guide](./../configuration/index.md). + +#### What if I don't have an imaging archive? + +We provide some guidance on configuring a local image archive in our +[Data Source Essentials](./../configuration/index.md#set-up-a-local-DICOM-server) +guide. Hosting an archive remotely is a little trickier. You can check out some +of our [advanced recipes](#recipes) for modeled setups that may work for you. + +#### What if I intend to host the OHIF Viewer at a different domain? + +There are two important steps to making sure this setup works: + +1. Your Image Archive needs to be exposed, in some way, to the open web. This + can be directly, or through a `reverse proxy`, but the Viewer needs _some + way_ to request its data. +2. \* Your Image Archive needs to have appropriate CORS (Cross-Origin Resource + Sharing) Headers + +> \* Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional +> HTTP headers to tell a browser to let a web application running at one origin +> (domain) have permission to access selected resources from a server at a +> different origin. - [MDN Web Docs: Web - Http - CORS][cors] + +Most image archives do not provide either of these features "out of the box". +It's common to use IIS, Nginx, or Apache to route incoming requests and append +appropriate headers. You can find an example of this setup in our +[Nginx + Image Archive Deployment Recipe](./nginx--image-archive.md). + +#### What if my archive doesn't support DicomWeb? + +It's possible to supply all Study data via JSON format, in the event you do not +have a DicomWeb endpoint. You can host all of the relevant files on any web +accessible server (Amazon S3, Azure Blob Storage, Local file server etc.) + +This JSON is supplied via the '?url=' query parameter. It should reference an +endpoint that returns **application/json** formatted text. + +If you do not have an API, you can simply return a text file containing the JSON +from any web server. + +You tell the OHIF viewer to use JSON by using the `dicomjson` datasource and +appending `'?url='` query to your mode's route: + +e.g. +`https://my-test-ohif-server/myMode/dicomjson?url=https://my-json-server/study-uid.json` + +The returned JSON object must contain a single root object with a 'studies' +array. + +You can read more about using different data sources for mode's routes +[here](../platform/modes/routes.md#route-path) + +_Sample JSON format:_ + +```json +{ + "studies": [ + { + "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78", + "StudyDescription": "BRAIN SELLA", + "StudyDate": "20010108", + "StudyTime": "120022", + "PatientName": "MISTER^MR", + "PatientId": "832040", + "series": [ + { + "SeriesDescription": "SAG T-1", + "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", + "SeriesNumber": 2, + "SeriesDate": "20010108", + "SeriesTime": "120318", + "Modality": "MR", + "instances": [ + { + "metadata": { + "Columns": 512, + "Rows": 512, + "InstanceNumber": 3, + "AcquisitionNumber": 0, + "PhotometricInterpretation": "MONOCHROME2", + "BitsAllocated": 16, + "BitsStored": 16, + "PixelRepresentation": 1, + "SamplesPerPixel": 1, + "PixelSpacing": [0.390625, 0.390625], + "HighBit": 15, + "ImageOrientationPatient": [0, 1, 0, 0, 0, -1], + "ImagePositionPatient": [11.6, -92.5, 98.099998], + "FrameOfReferenceUID": "1.2.840.113619.2.5.1762583153.223134.978956938.470", + "ImageType": ["ORIGINAL", "PRIMARY", "OTHER"], + "Modality": "MR", + "SOPInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.124", + "SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121", + "StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78" + }, + "url": "dicomweb://s3.amazonaws.com/lury/MRStudy/1.2.840.113619.2.5.1762583153.215519.978957063.124.dcm" + } + ] + } + ] + } + ] +} +``` + +More info on this JSON format can be found here +[Issue #1500](https://github.com/OHIF/Viewers/issues/1500) + +**Implementation Notes:** + + + +1. For each instance url (dicom object) in the returned JSON, you must prefix + the `url` with `dicomjson:` in order for the cornerstone image loader to + retrieve it correctly. eg. `https://image-server/my-image.dcm` ---> + `dicomjson:https://image-server/my-image.dcm` +2. The JSON format above is compatible with >= v3.7.8 of the application in `V2` + version. Older versions of the viewer used a different JSON format. As of + 20/04/20 the public [https://viewer.ohif.org/] is a pre 3.0 version that does + not support this format yet. +3. The JSON format is case-sensitive. Please ensure you have matched casing with + the naturalised Dicom format referenced in + [Issue #1500](https://github.com/OHIF/Viewers/issues/1500). + +_CORS Issues (Cross-Origin Resource Sharing)_ + +If you host a JSON API or Images on a different domain from the app itself, +you will likely have CORS issues. This will also happen when testing from +Localhost and reaching out to remote servers. Even if the domain is the same, +different ports, subdomains or protocols (https vs http) will also cause CORS +errors. You will to need add a configuration on each server hosting these assets +to allow your App server origin. + +For example: + +Let's assume your application is hosted on `https://my-ohif-server.com`. + +Your JSON API is hosted on `https://my-json-api.aws.com` + +And your images are stored on Amazon S3 at `https://my-s3-bucket.aws.com` + +When you first start your application, browsing to +`https://my-ohif-server.com/myMode/dicomjson?url=https://my-json-api.aws.com/api/my-json-study-info.json`, +you will likely get a CORS error in the browser console as it tries to connect +to `https://my-json-api.aws.com`. + +Adding a setting on the JSON server to allow the CORS origin = +`https://my-ohif-server.com` should solve this. + +Next, you will likely get a similar CORS error, as the browser tries to go to +`https://my-s3-bucket.aws.com`. You will need to go to the S3 bucket +configuration, and add a CORS setting to allow origin = +`https://my-ohif-server.com`. + +Essentially, whenever the application connects to a remote resource, you will +need to add the applications url to the allowed CORS Origins on that resource. +Adding an origin similar to https://localhost:3000 will also allow for local +testing. + +### Securing Your Data + +Coming soon + + + +### Recipes + +We've included a few recipes for common deployment scenarios. There are many, +many possible configurations, so please don't feel limited to these setups. +Please feel free to suggest or contribute your own recipes. + +- [Build for Production](./build-for-production.md) +- [Static](./static-assets.md) +- [Nginx + Image Archive](./nginx--image-archive.md) +- [User Account Control](./user-account-control.md) + + + + +[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer +[pwa-url]: https://developers.google.com/web/progressive-web-apps/ +[static-assets-url]: https://www.maxcdn.com/one/visual-glossary/static-content/ +[app-store]: https://medium.freecodecamp.org/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned-7cb3f56daf9b +[dicom-web-standard]: https://www.dicomstandard.org/dicomweb/ +[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb +[host-static-assets]: https://www.netlify.com/blog/2016/05/18/9-reasons-your-site-should-be-static/ +[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +[code-flows]: https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660 +[code-sandbox]: https://codesandbox.io/s/viewer-script-tag-tprch + diff --git a/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md b/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md new file mode 100644 index 00000000000..79f729d2fb5 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/deployment/nginx--image-archive.md @@ -0,0 +1,273 @@ +--- +sidebar_position: 4 +--- + +# Nginx + Image Archive + +> DISCLAIMER! We make no claims or guarantees of this approach's security. If in +> doubt, enlist the help of an expert and conduct proper audits. + +At a certain point, you may want others to have access to your instance of the +OHIF Viewer and its medical imaging data. This post covers one of many potential +setups that accomplish that. Please note, noticeably absent is user account +control. + +Do not use this recipe to host sensitive medical data on the open web. Depending +on your company's policies, this may be an appropriate setup on an internal +network when protected with a server's basic authentication. For a more robust +setup, check out our [user account control recipe](./user-account-control) +that builds on the lessons learned here. + +## Overview + +Our two biggest hurdles when hosting our image archive and web client are: + +- Risks related to exposing our PACS to the network +- Cross-Origin Resource Sharing (CORS) requests + +### Handling Web Requests + +We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web +requests. Nginx is open source software for web serving, reverse proxying, +caching, and more. It's designed for maximum performance and stability -- +allowing us to more reliably serve content than Orthanc's built-in server can. + +More specifically, we accomplish this by using a +[`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve +resources from our image archive (Orthanc), and when accessing its web admin. + +> A reverse proxy is a type of proxy server that retrieves resources on behalf +> of a client from one or more servers. These resources are then returned to the +> client, appearing as if they originated from the proxy server itself. + +### CORS Issues + +Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to +tell a browser which web applications have permission to access selected +resources from a server at a different origin (domain, protocol, port). IE. By +default, a Web App located at `http://my-website.com` can't access resources +hosted at `http://not-my-website.com` + +We can solve this one of two ways: + +1. Have our Image Archive located at the same domain as our Web App +2. Add appropriate `Access-Control-Allow-*` HTTP headers + +**This solution uses the first approach.** + +You can read more about CORS in this Medium article: [Understanding +CORS][understanding-cors] + +### Diagram + +This setup allows us to create a setup similar to the one pictured below: + + +![nginX](../assets/img/nginx-image-archive.png) + + +- All web requests are routed through `nginx` on our `OpenResty` image +- `/pacs` is a reverse proxy for `orthanc`'s `DICOM Web` endpoints +- `/pacs-admin` is a reverse proxy for `orthanc`'s Web Admin +- All static resources for OHIF Viewer are served up by `nginx` when a matching + route for that resource is requested + +## Getting Started + +### Requirements + +- Docker + - [Docker for Mac](https://docs.docker.com/docker-for-mac/) + - [Docker for Windows](https://docs.docker.com/docker-for-windows/) + +_Not sure if you have `docker` installed already? Try running `docker --version` +in command prompt or terminal_ + +### Setup + +- Navigate to `viewer` folder inside `platform` +- then: `cd .recipes/OpenResty-Orthanc` +- run: `docker-compose up --build` +- Navigate to `127.0.0.1` for the viewer +- Navigate to `127.0.0.1/pacs-admin` for uploading studies + + +You can see the overview of the mentioned steps: + + + +
+ +
+ + + +### Troubleshooting + +_Exit code 137_ + +This means Docker ran out of memory. Open Docker Desktop, go to the `advanced` +tab, and increase the amount of Memory available. + +_Cannot create container for service X_ + +Use this one with caution: `docker system prune` + +_X is already running_ + +Stop running all containers: + +- Win: `docker ps -a -q | ForEach { docker stop $_ }` +- Linux: `docker stop $(docker ps -a -q)` + + +_Traceback (most recent call last):_ + _File "urllib3/connectionpool.py", line 670, in urlopen_ + _...._ + +Are you sure your docker is running? see explanation [here](https://github.com/docker/compose/issues/7896) + + +### Configuration + +After verifying that everything runs with default configuration values, you will +likely want to update: + +- The domain: `http://127.0.0.1` + +#### OHIF Viewer + +The OHIF Viewer's configuration is imported from a static `.js` file. The +configuration we use is set to a specific file when we build the viewer, and +determined by the env variable: `APP_CONFIG`. You can see where we set its value +in the `dockerfile` for this solution: + +`ENV APP_CONFIG=config/docker_openresty-orthanc.js` + +You can find the configuration we're using here: +`/public/config/docker_openresty-orthanc.js` + +To rebuild the `webapp` image created by our `dockerfile` after updating the +Viewer's configuration, you can run: + +- `docker-compose build` OR +- `docker-compose up --build` + +#### Other + +All other files are found in: `/docker/OpenResty-Orthanc/` + +| Service | Configuration | Docs | +| ----------------- | --------------------------------- | ------------------------------------------- | +| OHIF Viewer | [dockerfile][dockerfile] | You're reading them now! | +| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] | +| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] | + +## Next Steps + +### Deploying to Production + +While these configuration and docker-compose files model an environment suitable +for production, they are not easy to deploy "as is". You can either: + +- Manually recreate this environment and deploy built application files **OR** +- Deploy to a cloud kubernetes provider like + [Digital Ocean](https://www.digitalocean.com/products/kubernetes/) **OR** + - [See a full list of cloud providers here](https://landscape.cncf.io/category=cloud&format=card-mode&grouping=category) +- Find and follow your preferred provider's guide on setting up + [swarms and stacks](https://docs.docker.com/get-started/) + +### Adding SSL + +Adding SSL registration and renewal for your domain with Let's Encrypt that +terminates at Nginx is an incredibly important step toward securing your data. +Here are some resources, specific to this setup, that may be helpful: + +- [lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl) +- [Let's Encrypt + Nginx](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) + +While we terminate SSL at Nginx, it may be worth using self-signed certificates +for communication between services. + +- [SSL Termination for TCP Upstream Servers](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/) + +### Use PostgresSQL w/ Orthanc + +Orthanc can handle a large amount of data and requests, but if you find that +requests start to slow as you add more and more studies, you may want to +configure your Orthanc instance to use PostgresSQL. Instructions on how to do +that can be found in the +[`Orthanc Server Book`](http://book.orthanc-server.com/users/docker.html), under +"PostgreSQL and Orthanc inside Docker" + +### Improving This Guide + +Here are some improvements this guide would benefit from, and that we would be +more than happy to accept Pull Requests for: + +- SSL Support +- Complete configuration with `.env` file (or something similar) +- Any security issues +- One-click deploy to a cloud provider + +## Resources + +### Misc. Helpful Commands + +_Check if `nginx.conf` is valid:_ + +```bash +docker run --rm -t -a stdout --name my-openresty -v $PWD/config/:/usr/local/openresty/nginx/conf/:ro openresty/openresty:alpine-fat openresty -c /usr/local/openresty/nginx/conf/nginx.conf -t +``` + +_Interact w/ running container:_ + +`docker exec -it CONTAINER_NAME bash` + +_List running containers:_ + +`docker ps` + +### Referenced Articles + +For more documentation on the software we've chosen to use, you may find the +following resources helpful: + +- [Orthanc for Docker](http://book.orthanc-server.com/users/docker.html) +- [OpenResty Guide](http://www.staticshin.com/programming/definitely-an-open-resty-guide/) +- [Lua Ngx API](https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/) + +For a different take on this setup, check out the repositories our community +members put together: + +- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker) +- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker) + + + + + +[nginx]: https://www.nginx.com/resources/glossary/nginx/ +[understanding-cors]: https://medium.com/@baphemot/understanding-cors-18ad6b478e2b +[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration +[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc + +[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/dockerfile +[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/nginx.conf +[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc/config/orthanc.json + diff --git a/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md b/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md new file mode 100644 index 00000000000..767ad692802 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/deployment/static-assets.md @@ -0,0 +1,160 @@ +--- +sidebar_position: 3 +--- + +# Deploy Static Assets + +> WARNING! All of these solutions stand-up a publicly accessible web viewer. Do +> not hook your hosted viewer up to a sensitive source of data without +> implementing authentication. + +There are a lot of options for deploying static assets. Some services, like +`netlify` and `surge.sh`, specialize in static websites. You'll notice that +deploying with them requires much less time and effort, but comes at the cost of +less product offerings. + +While not required, it can simplify things to host your Web Viewer alongside +your image archive. Services with more robust product offerings, like +`Google Cloud`, `Microsoft's Azure`, and `Amazon Web Services (AWS)`, are able +to accommodate this setup. + +_Drag-n-drop_ + +- [Netlify: Drop](#netlify-drop) + +_Easy_ + +- [Surge.sh](#surgesh) +- [GitHub Pages](#github-pages) + +_Advanced_ + +- [Deploy Static Assets](#deploy-static-assets) + - [Drag-n-drop](#drag-n-drop) + - [Netlify Drop](#netlify-drop) + - [Easy](#easy) + - [Surge.sh](#surgesh) + - [GitHub Pages](#github-pages) + - [Advanced](#advanced) + - [AWS S3 + Cloudfront](#aws-s3--cloudfront) + - [GCP + Cloudflare](#gcp--cloudflare) + - [Azure](#azure) + +## Drag-n-drop + +### Netlify Drop + + +
+ +
+ + +_GIF demonstrating deployment with Netlify Drop_ + +1. https://app.netlify.com/drop +2. Drag your `build/` folder on to the drop target +3. ... +4. _annnd you're done_ + +**Features:** + +- Custom domains & HTTPS +- Instant Git integration +- Continuous deployment +- Deploy previews +- Access to add-ons + +(Non-free tiers include identity, FaaS, Forms, etc.) + +Learn more about [Netlify on their website](https://www.netlify.com/) + +## Easy + +### Surge.sh + +> Static web publishing for Front-End Developers. Simple, single-command web +> publishing. Publish HTML, CSS, and JS for free, without leaving the command +> line. + +![surge.sh deploy example](../assets/img/surge-deploy.gif) + +_GIF demonstrating deployment with surge_ + +```shell +# Add surge command +yarn global add surge + +# In the build directory +surge +``` + +**Features:** + +- Free custom domain support +- Free SSL for surge.sh subdomains +- pushState support for single page apps +- Custom 404.html pages +- Barrier-free deployment through the CLI +- Easy integration into your Grunt toolchain +- Cross-origin resource support +- And more… + +Learn more about [surge.sh on their website](https://surge.sh/) + +### GitHub Pages + +> WARNING! While great for project sites and light use, it is not advised to use +> GitHub Pages for production workloads. Please consider using a different +> service for mission critical applications. + +> Websites for you and your projects. Hosted directly from your GitHub +> repository. Just edit, push, and your changes are live. + +This deployment strategy makes more sense if you intend to maintain your project in +a GitHub repository. It allows you to specify a `branch` or `folder` as the +target for a GitHub Page's website. As you push code changes, the hosted content +updates to reflect those changes. + +1. Head over to GitHub.com and create a new repository, or go to an existing + one. Click on the Settings tab. +2. Scroll down to the GitHub Pages section. Choose the `branch` or `folder` you + would like as the "root" of your website. +3. Fire up a browser and go to `http://username.github.io/repository` + +Configuring Your Site: + +- [Setting up a custom domain](https://help.github.com/en/articles/using-a-custom-domain-with-github-pages) +- [Setting up SSL](https://help.github.com/en/articles/securing-your-github-pages-site-with-https) + +Learn more about [GitHub Pages on its website](https://pages.github.com/) + +## Advanced + +All of these options, while using providers with more service offerings, +demonstrate how to host the viewer with their respective file storage and CDN +offerings. While you can serve your static assets this way, if you're going +through the trouble of using AWS/GCP/Azure, it's more likely you're doing so to +avoid using a proxy or to simplify authentication. + +If that is the case, check out some of our more advanced `docker` deployments +that target these providers from the left-hand sidepanel. + +These guides can be a bit longer and an update more frequently. To provide +accurate documentation, we will link to each provider's own recommended steps: + +### AWS S3 + Cloudfront + +- [Host a Static Website](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) +- [Speed Up Your Website with Cloudfront](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-cloudfront-walkthrough.html) + +### GCP + Cloudflare + +- [Things to Know Before Getting Started](https://code.luasoftware.com/tutorials/google-cloud-storage/things-to-know-before-hosting-static-website-on-google-cloud-storage/) +- [Hosting a Static Website on GCP](https://cloud.google.com/storage/docs/hosting-static-website) + +### Azure + +- [Host a Static Website](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website) +- [Add SSL Support](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-https-custom-domain-cdn) +- [Configure a Custom Domain](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-custom-domain-name) diff --git a/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md b/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md new file mode 100644 index 00000000000..e411cc7d535 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/deployment/user-account-control.md @@ -0,0 +1,7 @@ +--- +sidebar_position: 5 +--- + +# User Account Control + +> Coming soon - We are working on bringing the User Account Control to OHIF-v3 diff --git a/platform/docs/versioned_docs/version-3.0/development/_category_.json b/platform/docs/versioned_docs/version-3.0/development/_category_.json new file mode 100644 index 00000000000..8627cac4920 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Development", + "position": 5 +} diff --git a/platform/docs/versioned_docs/version-3.0/development/architecture.md b/platform/docs/versioned_docs/version-3.0/development/architecture.md new file mode 100644 index 00000000000..4bcebb0175e --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/architecture.md @@ -0,0 +1,203 @@ +--- +sidebar_position: 2 +sidebar_label: Architecture +--- + +# Architecture + +In order to achieve a platform that can support various workflows and be +extensible for the foreseeable future we went through extensive planning of +possible use cases and decided to significantly change and improve the +architecture. + +Below, we aim to demystify that complexity by providing insight into how +`OHIF Platform` is architected, and the role each of its dependent libraries +plays. + +## Overview + +The [OHIF Medical Image Viewing Platform][viewers-project] is maintained as a +[`monorepo`][monorepo]. This means that this repository, instead of containing a +single project, contains many projects. If you explore our project structure, +you'll see the following: + +```bash +│ +├── extensions +│ ├── _example # Skeleton of example extension +│ ├── default # default functionalities +│ ├── cornerstone # 2D images w/ Cornerstone.js +│ ├── measurement-tracking # measurement tracking +│ ├── dicom-sr # Structured reports +│ └── dicom-pdf # View DICOM wrapped PDFs in viewport +│ +├── modes +│ └── longitudinal # longitudinal measurement tracking mode +│ +├── platform +│ ├── core # Business Logic +│ ├── i18n # Internationalization Support +│ ├── ui # React component library +│ └── viewer # Connects platform and extension projects +│ +├── ... # misc. shared configuration +├── lerna.json # MonoRepo (Lerna) settings +├── package.json # Shared devDependencies and commands +└── README.md +``` + +OHIF v3 is composed of the following components, described in detail in further +sections: + +- `@ohif/viewer`: The core framework that controls extension registration, mode + composition and routing. +- `@ohif/core`: A library of useful and reusable medical imaging functionality + for the web. +- `@ohif/ui`: A library of reusable components to build OHIF-styled applications + with. +- `Extensions`: A set of building blocks for building applications. The OHIF org + maintains a few core libraries. +- `Modes`: Configuration objects that tell @ohif/viewer how to compose + extensions to build applications on different routes of the platform. + +## Extensions + +The `extensions` directory contains many packages that provide essential +functionalities such as rendering, study/series browsers, measurement tracking +that modes can consume to enable a certain workflow. Extensions have had their +behavior changed in `OHIF-v3` and their api is expanded. In summary: + +> In `OHIF-v3`, extensions no longer automatically hook themselves to the app. +> Now, registering an extension makes its component available to `modes` that +> wish to use them. Basically, extensions in `OHIF-v3` are **building blocks** +> for building applications. + +OHIF team maintains several high value and commonly used functionalities in its +own extensions. For a list of extensions maintained by OHIF, +[check out this helpful table](../platform/extensions/index.md#maintained-extensions). +As an example `default` extension provides a default viewer layout, a +study/series browser and a datasource that maps to a DICOMWeb compliant backend. + +[Click here to read more about extensions!](../platform/extensions/index.md) + +## Modes + +The `modes` directory contains workflows that can be registered with OHIF within +certain `routes`. The mode will get used once the user opens the viewer on the +registered route. + +OHIF extensions were designed to provide certain core functionalities for +building your viewer. However, often in medical imaging we face a specific use +case in which we are using some core functionalities, adding our specific UI, +and use it in our workflows. Previously, to achieve this you had to create an +extension to add have such feature. `OHIF-v3` introduces `Modes` to enable +building such workflows by re-using the core functionalities from the +extensions. + +Some common workflows may include: + +- Measurement tracking for lesions +- Segmentation of brain abnormalities +- AI probe mode for detecting prostate cancer + +In the mentioned modes above, they will share the same core rendering module +that the `default` extension provides. However, segmentation mode will require +segmentation tools which is not needed for the other two. As you can see, modes +are a layer on top of extensions, that you can configure in order to achieve +certain workflows. + +To summarize the difference between extensions and modes in `OHIF-v3` and +extensions in `OHIF-v2` + +> - `Modes` are configuration objects that tell _@ohif/viewer_ how to compose +> extensions to build applications on different routes of the platform. +> - In v2 extensions are “plugins” that add functionality to a core viewer. +> - In v3 extensions are building blocks that a mode uses to build an entire +> viewer layout. + +[Click here to read more about modes!](../platform/modes/index.md) + +## Platform + +### `@ohif/viewer` + +This library is the core library which consumes modes and extensions and builds +an application. Extensions can be passed in as app configuration and will be +consumed and initialized at the appropriate time by the application. Upon +initialization the viewer will consume extensions and modes and build up the +route desired, these can then be accessed via the study list, or directly via +url parameters. + +Upon release modes will also be plugged into the app via configuration, but this +is still an area which is under development/discussion, and they are currently +pulled from the window in beta. + +Future ideas for this framework involve only adding modes and fetching the +required extension versions at either runtime or build time, but this decision +is still up for discussion. + +### `@ohif/core` + +OHIF core is a carefully maintained and tested set of web-based medical imaging +functions and classes. This library includes managers and services used from +within the viewer app. + +OHIF core is largely similar to the @ohif/core library in v2, however a lot of +logic has been moved to extensions: however all logic about DICOMWeb and other +data fetching mechanisms have been pulled out, as these now live in extensions, +described later. + +### `@ohif/ui` + +Firstly, a large time-consumer/barrier for entry we discovered was building new +UI in a timely manner that fit OHIF’s theme. For this reason we have built a new +UI component library which contains all the components one needs to build their +own viewer. + +These components are presentational only, so you can reuse them with whatever +logic you desire. As the components are presentational, you may swap out +@ohif/ui for a custom UI library with conforming API if you wish to white label +the viewer. The UI library is here to make development easier and quicker, but +it is not mandatory for extension components to use. + +[Check out our component library!](https://react.ohif.org/) + +## Overview of the architecture + +OHIF-v3 architecture can be seen in the following figure. We will explore each +piece in more detail. + +![mode-archs](../assets/img/mode-archs.png) + +## Common Questions + +> Can I create my own Viewer using Vue.js or Angular.js? + +You can, but you will not be able to leverage as much of the existing code and +components. `@ohif/core` could still be used for business logic, and to provide +a model for extensions. `@ohif/ui` would then become a guide for the components +you would need to recreate. + +> When I want to implement a functionality, should it be in the mode or in an +> extension? + +This is a great question. Modes are designed to consume extensions, so you +should implement your functionality in one of the modules of your new extension, +and let the mode consume it. This way, in the future, if you needed another mode +that utilizes the same functionality, you can easily hook the extension to the +new mode as well. + + + + +[monorepo]: https://github.com/OHIF/Viewers/issues/768 +[viewers-project]: https://github.com/OHIF/Viewers +[viewer-npm]: https://www.npmjs.com/package/@ohif/viewer +[pwa]: https://developers.google.com/web/progressive-web-apps/ +[configuration]: ../configuration/index.md +[extensions]: ../platform/extensions/index.md +[core-github]: https://github.com/OHIF/viewers/platform/core +[ui-github]: https://github.com/OHIF/Viewers/tree/master/platform/ui + diff --git a/platform/docs/versioned_docs/version-3.0/development/continous-integration.md b/platform/docs/versioned_docs/version-3.0/development/continous-integration.md new file mode 100644 index 00000000000..3c3124af83b --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/continous-integration.md @@ -0,0 +1,90 @@ +--- +sidebar_position: 7 +sidebar_label: Continous Integration +--- + +# Continous Integration (CI) + +This repository uses `CircleCI` and `Netlify` for continous integration. + +## Deploy Previews + +[Netlify Deploy previews][deploy-previews] are generated for every pull request. +They allow pull request authors and reviewers to "Preview" the OHIF Viewer as if +the changes had been merged. + +Deploy previews can be configured by modifying the `netlify.toml` file in the +root of the repository. Some additional scripts/assets for netlify are included +in the root `.netlify` directory. + +## Workflows + +[CircleCI Workflows][circleci-workflows] are a set of rules for defining a +collection of jobs and their run order. They are self-documenting and their +configuration can be found in our CircleCI configuration file: +`.circleci/config.yml`. + +### Workflow: PR_CHECKS + +The PR_CHECKS workflow (Pull Request Checks) runs our automated unit and +end-to-end tests for every code check-in. These tests must all pass before code +can be merged to our `master` branch. + +![PR_CHECKS](../assets/img/WORKFLOW_PR_CHECKS.png) + +### Workflow: PR_OPTIONAL_DOCKER_PUBLISH + +The PR_OPTIONAL_DOCKER_PUBLISH workflow allows for "manual approval" to publish +the pull request as a tagged docker image. This is helpful when changes need to +be tested with the Google Adapter before merging to `master`. + +![PR_Workflow](../assets/img/WORKFLOW_PR_OPTIONAL_DOCKER_PUBLISH.png) + +> NOTE: This workflow will fail unless it's for a branch on our `upstream` +> repository. If you need this functionality, but the branch is from a fork, +> merge the changes to a short-lived `feature/` branch on `upstream` + +### Workflow: DEPLOY + +The DEPLOY workflow deploys the OHIF Viewer when changes are merged to master. +It uses the Netlify CLI to deploy assets created as part of the repository's PWA +Build process (`yarn run build`). The workflow allows for "Manual Approval" to +promote the build to `STAGING` and `PRODUCTION` environments. + +![WORKFLOW_DEPLOY](../assets/img/WORKFLOW_DEPLOY.png) + +| Environment | Description | URL | +| ----------- | ---------------------------------------------------------------------------------- | --------------------------------------------- | +| Development | Always reflects latest changes on `master` branch. | [Netlify][netlify-dev] / [OHIF][ohif-dev] | +| Staging | For manual testing before promotion to prod. Keeps development workflow unblocked. | [Netlify][netlify-stage] / [OHIF][ohif-stage] | +| Production | Stable, tested, updated less frequently. | [Netlify][netlify-prod] / [OHIF][ohif-prod] | + +### Workflow: RELEASE + +The RELEASE workflow publishes our `npm` packages, updated documentation, and +`docker` image when changes are merged to master. `Lerna` and "Semantic Commit +Syntax" are used to independently version and publish the many packages in our +monorepository. If a new version is cut/released, a Docker image is created. +Documentation is generated with `gitbook` and pushed to our `gh-pages` branch. +GitHub hosts the `gh-pages` branch with GitHub Pages. + +- Platform Packages: https://github.com/ohif/viewers/#platform +- Extension Packages: https://github.com/ohif/viewers/#extensions +- Documentation: https://docs.ohif.org/ + +![WORKFLOW_RELEASE](../assets/img/WORKFLOW_RELEASE.png) + + + + +[deploy-previews]: https://www.netlify.com/blog/2016/07/20/introducing-deploy-previews-in-netlify/ +[circleci-workflows]: https://circleci.com/docs/2.0/workflows/ +[netlify-dev]: https://ohif-dev.netlify.com +[netlify-stage]: https://ohif-stage.netlify.com +[netlify-prod]: https://ohif-prod.netlify.com +[ohif-dev]: https://viewer-dev.ohif.org +[ohif-stage]: https://viewer-stage.ohif.org +[ohif-prod]: https://viewer-prod.ohif.org + diff --git a/platform/docs/versioned_docs/version-3.0/development/contributing.md b/platform/docs/versioned_docs/version-3.0/development/contributing.md new file mode 100644 index 00000000000..763be20dd08 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/contributing.md @@ -0,0 +1,152 @@ +--- +sidebar_position: 4 +sidebar_label: Contributing +--- + +# Contributing + +## How can I help? + +Fork the repository, make your change and submit a pull request. If you would +like to discuss the changes you intend to make to clarify where or how they +should be implemented, please don't hesitate to create a new issue. At a +minimum, you may want to read the following documentation: + +- [Getting Started](/development/getting-started.md) +- [Architecture](./architecture.md) + +Pull requests that are: + +- Small +- [Well tested](./testing.md) +- Decoupled + +Are much more likely to get reviewed and merged in a timely manner. + +## When changes impact multiple repositories + +While this can be tricky, we've tried to reduce how often this situation crops +up this with our [recent switch to a monorepo][monorepo]. Our maintained +extensions, ui components, internationalization library, and business logic can +all be developed by simply running `yarn run dev` from the repository root. + +Testing the viewer with locally developed, unpublished package changes from a +package outside of the monorepo is most common with extension development. Let's +demonstrate how to accomplish this with two commonly forked extension +dependencies: + +### `cornerstone-tools` + +On your local file system: + +```bash title="/my-projects/" +├── cornerstonejs/cornerstone-tools +└── ohif/viewers +``` + +- Open a terminal/shell +- Navigate to `cornerstonejs/cornerstone-tools` + - `yarn install` + - [`yarn link`](https://yarnpkg.com/en/docs/cli/link) + - `yarn run dev` + +* Open a new terminal/shell +* Navigate to `ohif/viewers` (the root of ohif project) + - `yarn install` + - [`yarn link cornerstone-tools`](https://yarnpkg.com/en/docs/cli/link) + - `yarn run dev` + +As you make changed to `cornerstone-tools`, and it's output is rebuilt, you +should see the following behavior: + +![tools](..//assets/img/cornerstone-tools-link.gif) + +If you wish to stop using your local package, run the following commands in the +`ohif/viewers` repository root: + +- `yarn unlink cornerstone-tools` +- `yarn install --force` + + + +#### Other linkage notes + +We're still working out some of the kinks with local package development as +there are a lot of factors that can influence the behavior of our development +server and bundler. If you encounter issues not addressed here, please don't +hesitate to reach out on GitHub. + +Sometimes you might encounter a situation where the linking doesn't work as +expected. This might happen when there are multiple linked packages with the +same name. You can [remove][unlink] the linked packages inside yarn and try +again. + +## Any guidance on submitting changes? + +While we do appreciate code contributions, triaging and integrating contributed +code changes can be very time consuming. Please consider the following tips when +working on your pull requests: + +- Functionality is appropriate for the repository. Consider creating a GitHub + issue to discuss your suggested changes. +- The scope of the pull request is not too large. Please consider separate pull + requests for each feature as big pull requests are very time consuming to + understand. + +We will provide feedback on your pull requests as soon as possible. Following +the tips above will help ensure your changes are reviewed. + + + + + + +[example-url]: https://deploy-preview-237--ohif.netlify.com/viewer/?url=https://s3.eu-central-1.amazonaws.com/ohif-viewer/sampleDICOM.json +[pr-237]: https://github.com/OHIF/Viewers/pull/237 +[monorepo]: https://github.com/OHIF/Viewers/issues/768 +[unlink]: https://stackoverflow.com/questions/58459698/is-there-a-command-to-unlink-all-yarn-packages-yarn-unlink-all + diff --git a/platform/docs/versioned_docs/version-3.0/development/getting-started.md b/platform/docs/versioned_docs/version-3.0/development/getting-started.md new file mode 100644 index 00000000000..b9b3bc5d51a --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/getting-started.md @@ -0,0 +1,111 @@ +--- +sidebar_position: 1 +sidebar_label: Getting Started +--- + +# Getting Started + +## Setup + +### Fork & Clone + +If you intend to contribute back changes, or if you would like to pull updates +we make to the OHIF Viewer, then follow these steps: + +- [Fork][fork-a-repo] the [OHIF/Viewers][ohif-viewers-repo] repository +- [Create a local clone][clone-a-repo] of your fork + - `git clone https://github.com/YOUR-USERNAME/Viewers` +- Add OHIF/Viewers as a [remote repository][add-remote-repo] labeled `upstream` + - Navigate to the cloned project's directory + - `git remote add upstream https://github.com/OHIF/Viewers.git` + +With this setup, you can now [sync your fork][sync-changes] to keep it +up-to-date with the upstream (original) repository. This is called a "Triangular +Workflow" and is common for Open Source projects. The GitHub blog has a [good +graphic that illustrates this setup][triangular-workflow]. + +### Private + +Alternatively, if you intend to use the OHIF Viewer as a starting point, and you +aren't as concerned with syncing updates, then follow these steps: + +1. Navigate to the [OHIF/Viewers][ohif-viewers] repository +2. Click `Clone or download`, and then `Download ZIP` +3. Use the contents of the `.zip` file as a starting point for your viewer + +> NOTE: It is still possible to sync changes using this approach. However, +> submitting pull requests for fixes and features are best done with the +> separate, forked repository setup described in "Fork & Clone" + +## Developing + +### Requirements + +- [Node.js & NPM](https://nodejs.org/en/) +- [Yarn](https://yarnpkg.com/en/) +- Yarn workspaces should be enabled: + - `yarn config set workspaces-experimental true` + +### Kick the tires + +Navigate to the root of the project's directory in your terminal and run the +following commands: + +```bash +# Switch to the v3 branch +git switch v3-stable + +# Restore dependencies +yarn install + +# Start local development server +yarn run dev +``` + +You should see the following output: + +```bash +@ohif/viewer: i 「wds」: Project is running at http://localhost:3000/ +@ohif/viewer: i 「wds」: webpack output is served from / +@ohif/viewer: i 「wds」: Content not from webpack is served from D:\code\ohif\Viewers\platform\viewer +@ohif/viewer: i 「wds」: 404s will fallback to /index.html + +# And a list of all generated files +``` + +### 🎉 Celebrate 🎉 + +
+ +
+ +### Building for Production + +> More comprehensive guides for building and publishing can be found in our +> [deployment docs](./../deployment/index.md) + +```bash +# Build static assets to host a PWA +yarn run build +``` + +## Troubleshooting + +- If you receive a _"No Studies Found"_ message and do not see your studies, try + changing the Study Date filters to a wider range. +- If you see a 'Loading' message which never resolves, check your browser's + JavaScript console inside the Developer Tools to identify any errors. + + + + +[fork-a-repo]: https://help.github.com/en/articles/fork-a-repo +[clone-a-repo]: https://help.github.com/en/articles/fork-a-repo#step-2-create-a-local-clone-of-your-fork +[add-remote-repo]: https://help.github.com/en/articles/fork-a-repo#step-3-configure-git-to-sync-your-fork-with-the-original-spoon-knife-repository +[sync-changes]: https://help.github.com/en/articles/syncing-a-fork +[triangular-workflow]: https://github.blog/2015-07-29-git-2-5-including-multiple-worktrees-and-triangular-workflows/#improved-support-for-triangular-workflows +[ohif-viewers-repo]: https://github.com/OHIF/Viewers +[ohif-viewers]: https://github.com/OHIF/Viewers + diff --git a/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md b/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md new file mode 100644 index 00000000000..cf8af80f581 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/ohif-cli.md @@ -0,0 +1,291 @@ +--- +sidebar_position: 3 +sidebar_label: OHIF CLI +--- + +# OHIF Command Line Interface + +OHIF-v3 architecture has been re-designed to enable building applications that +are easily extensible to various use cases (Modes) that behind the scene would +utilize desired functionalities (Extensions) to reach the goal of the use case. +Now, the question is _how to create/remove/install/uninstall an extension and/or +mode?_ + +You can use the `cli` script that comes with the OHIF monorepo to achieve these +goals. + +:::note Info +In the long-term, we envision our `cli` tool to be a separate installable +package that you can invoke anywhere on your local system to achieve the same +goals. In the meantime, `cli` will remain as part of the OHIF monorepo and needs +to be invoked using the `yarn` command. +::: + + +## CLI Installation + +You don't need to install the `cli` currently. You can use `yarn` to invoke its +commands. + +## Commands + +:::note Important +All commands should run from the root of the monorepo. +::: + + +There are various commands that can be used to interact with the OHIF-v3 CLI. If +you run the following command, you will see a list of available commands. + +``` +yarn run cli --help +``` + +which will output + +``` +OHIF CLI + +Options: + -V, --version output the version number + -h, --help display help for command + +Commands: + create-extension Create a new template extension + create-mode Create a new template Mode + add-extension [version] Adds an ohif extension + remove-extension removes an ohif extension + add-mode [version] Removes an ohif mode + remove-mode Removes an ohif mode + link-extension Links a local OHIF extension to the Viewer to be used for development + unlink-extension Unlinks a local OHIF extension from the Viewer + link-mode Links a local OHIF mode to the Viewer to be used for development + unlink-mode Unlinks a local OHIF mode from the Viewer + list List Added Extensions and Modes + search [options] Search NPM for the list of Modes and Extensions + help [command] display help for command +``` + +As seen there are commands for you such as: `create-extension`, `create-mode`, +`add-extension`, `remove-extension`, `add-mode`, `remove-mode`, +`link-extension`, `unlink-extension`, `link-mode`, `unlink-mode`, `list`, +`search`, and `help`. Here we will go through each of the commands and describe +them. + +### create-mode + +If you need to create a new mode, you can use the `create-mode` command. This +command will create a new mode template in the directory that you specify. +The command will ask you couple of information/questions in order +to properly create the mode metadata in the `package.json` file. + +```bash +yarn run cli create-mode +``` + +
+ +![image](../assets/img/create-mode.png) + + +
+ +Note 1: Some questions have a default answer, which is indicated inside the +parenthesis. If you don't want to answer the question, just hit enter. It will +use the default answer. + +Note 2: As you see in the questions, you can initiate a git repository for the +new mode right away by answering `Y` (default) to the question. + +Note 3: Finally, as indicated by the green lines at the end, `create-mode` command only +create the mode template. You will need to link the mode to the Viewer in order +to use it. See the [`link-mode`](#link-mode) command. + +If we take a look at the directory that we created, we will see the following +files: + +
+ +![image](../assets/img/mode-template.png) + +
+ + +### create-extension + +Similar to the `create-extension` command, you can use the `create-extension` +command to create a new extension template. This command will create a new +extension template in the directory that you specify the path. + +```bash +yarn run cli create-extension +``` + + +Note: again similar to the `create-extension` command, you need to manually link +the extension to the Viewer in order to use it. See the +[`link-mode`](#link-mode) command. + + +### link-extension + +`link-extension` command will link a local OHIF extension to the Viewer. This +command will utilize `yarn link` to achieve so. + +```bash +yarn run cli link-extension +``` + +### unlink-extension + +There might be situations where you want to unlink an extension from the Viewer +after some developments. `unlink-extension` command will do so. + +```bash +ohif-cli unlink-extension +``` + + + +### link-mode + +Similar to the `link-extension` command, `link-mode` command will link a local +OHIF mode to the Viewer. + +```bash +yarn run cli link-mode +``` + +### unlink-mode + +Similar to the `unlink-extension` command, `unlink-mode` command will unlink a +local OHIF mode from the Viewer. + +```bash +ohif-cli unlink-mode +``` + +### add-mode + +OHIF is a modular viewer. This means that you can install (add) different modes +to the viewer if they are published online . `add-mode` command will add a new mode to +the viewer. It will look for the mode in the NPM registry and installs it. This +command will also add the extension dependencies that the mode relies on to the +Viewer (if specified in the peerDependencies section of the package.json). + +:::note Important +`cli` will validate the npm package before adding it to the Viewer. An OHIF mode +should have `ohif-mode` as one of its keywords. +::: + +Note: If you don't specify the version, the latest version will be used. + +```bash +yarn run cli add-mode [version] +``` + +For instance `@ohif-test/mode-clock` is an example OHIF mode that we have +published to NPM. This mode basically has a panel that shows the clock :) + +We can add this mode to the Viewer by running the following command: + +```bash +yarn run cli add-mode @ohif-test/mode-clock +``` + +After installation, the Viewer has a new mode! + + +![image](../assets/img/add-mode.png) + + +Note: If the mode has an extension peerDependency (in this case @ohif-test/extension-clock), +`cli` will automatically add the extension to the Viewer too. + +The result + +![image](../assets/img/clock-mode.png) +![image](../assets/img/clock-mode1.png) + +### add-extension + +This command will add an OHIF extension to the Viewer. It will look for the +extension in the NPM registry and install it. + +```bash +yarn run cli add-extension [version] +``` + + +### remove-mode + +This command will remove the mode from the Viewer and also remove the extension +dependencies that the mode relies on from the Viewer. + +```bash +yarn run cli remove-mode +``` + + +### remove-extension + +Similar to the `remove-mode` command, this command will remove the extension +from the Viewer. + +```bash +yarn run cli remove-extension +``` + +### list + +`list` command will list all the installed extensions and modes in +the Viewer. It uses the `PluginConfig.json` file to list the installed +extensions and modes. + +```bash +yarn run cli list +``` + +an output would look like this: + +
+ +![image](../assets/img/ohif-cli-list.png) + +
+ +### search + +Using `search` command, you can search for OHIF extensions and modes +in the NPM registry. This tool can accept a `--verbose` flag to show more +information about the results. + +```bash +yarn run cli search [--verbose] +``` + +
+ +![image](../assets/img/cli-search-no-verbose.png) + +
+ +with the verbose flag `ohif-cli search --verbose` you will achieve the following +output: + +
+ +![image](../assets/img/cli-search-with-verbose.png) + +
+ + +## PluginConfig.json + +To make all the above commands work, we have created a new file called `PluginConfig.json` which contains the +information needed to run the commands. You **don't need to (and should not)** +edit/update/modify this file as it is automatically generated by the CLI. You +can take a look at what this file contains by going to +`platform/viewer/PluginConfig.json` in your project's root directory. In short, +this file tracks and stores all the extensions/modes and the their version that +are currently being used by the viewer. diff --git a/platform/docs/versioned_docs/version-3.0/development/our-process.md b/platform/docs/versioned_docs/version-3.0/development/our-process.md new file mode 100644 index 00000000000..d8b8ed86217 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/our-process.md @@ -0,0 +1,156 @@ +--- +sidebar_position: 5 +sidebar_label: Issue & PR Triage Process +--- + +# Our Process + +Our process is a living, breathing thing. We strive to have regular +[retrospectives][retrospective] that help us shape and adapt our process to our +team's current needs. This document attempts to capture the broad strokes of +that process in an effort to: + +- Strengthen community member involvement and understanding +- Welcome feedback and helpful suggestions + +## Issue Triage + +[GitHub issues][gh-issues] are the best way to provide feedback, ask questions, +and suggest changes to the OHIF Viewer's core team. Community issues generally +fall into one of three categories, and are marked with a `triage` label when +created. + +| Issue Template Name | Description | +| ---------------------- | ---------------------------------------------------------------------------------------- | +| Community: Report 🐛 | Describe a new issue; Provide steps to reproduce; Expected versus actual result? | +| Community: Request ✋ | Describe a proposed new feature. Why should it be implemented? What is the impact/value? | +| Community: Question ❓ | Seek clarification or assistance relevant to the repository. | + +_table 1. issue template names and descriptions_ + +Issues that require `triage` are akin to support tickets. As this is often our +first contact with would-be adopters and contributors, it's important that we +strive for timely responses and satisfactory resolutions. We attempt to +accomplish this by: + +1. Responding to issue requiring `triage` at least once a week +2. Create new "official issues" from "community issues" +3. Provide clear guidance and next steps (when applicable) +4. Regularly clean up old (stale) issues + +> 🖋 Less obviously, patterns in the issues being reported can highlight areas +> that need improvement. For example, users often have difficulty navigating +> CORS issues when deploying the OHIF Viewer -- how do we best reduce our ticket +> volume for this issue? + +### Backlogged Issues + +Community issues serve as vehicles of discussion that lead us to "backlogged +issues". Backlogged issues are the distilled and actionable information +extracted from community issues. They contain the scope and requirements +necessary for hand-off to a core-team (or community) contributor ^\_^ + +| Category | Description | Labels | +| -------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| Bugs | An issue with steps that produce a bug (an unexpected result). | [Bug: Verified 🐛][label-bug] | +| Stories | A feature/enhancement with a clear benefit, boundaries, and requirements. | [Story 🙌][label-story] | +| Tasks | Changes that improve [UX], [DX], or test coverage; but don't impact application behavior | [Task: CI/Tooling 🤖][label-tooling], [Task: Docs 📖][label-docs], [Task: Refactor 🛠][label-refactor], [Task: Tests 🔬][label-tests] | + +_table 2. backlogged issue types ([full list of labels][gh-labels])_ + +## Issue Curation (["backlog grooming"][groom-backlog]) + +If a [GitHub issue][gh-issues] has a `bug`, `story`, or `task` label; it's on +our backlog. If an issue is on our backlog, it means we are, at the very least, +committed to reviewing any community drafted Pull Requests to complete the +issue. If you're interested in seeing an issue completed but don't know where to +start, please don't hesitate to leave a comment! + +While we don't yet have a long-term or quarterly road map, we do regularly add +items to our ["Active Development" GitHub Project Board][gh-board]. Items on +this project board are either in active development by Core Team members, or +queued up for development as in-progress items are completed. + +> 🖋 Want to contribute but not sure where to start? Check out [Up for +> grabs][label-grabs] issues and our [Contributing +> documentation][contributing-docs] + +## Contributions (Pull Requests) + +Incoming Pull Requests (PRs) are triaged using the following labels. Code review +is performed on all PRs where the bug fix or added functionality is deemed +appropriate: + +| Labels | Description | +| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| **Classification** | | +| [PR: Bug Fix][label-bug] | Filed to address a Bug. | +| [PR: Draft][draft] | Filed to gather early feedback from the core team, but which is not intended for merging in the short term. | +| **Review Workflow** | | +| [PR: Awaiting Response 💬][awaiting-response] | The core team is waiting for additional information from the author. | +| [PR: Awaiting Review 👀][awaiting-review] | The core team has not yet performed a code review. | +| [PR: Awaiting Revisions 🖊][awaiting-revisions] | Following code review, this label is applied until the author has made sufficient changes. | +| **QA** | | +| [PR: Awaiting User Cases 💃][awaiting-stories] | The PR code changes need common language descriptions of impact to end users before the review can start | +| [PR: No UX Impact 🙃][no-ux-impact] | The PR code changes do not impact the user's experience | + +We rely on GitHub Checks and integrations with third party services to evaluate +changes in code quality and test coverage. Tests must pass and User cases must +be present (when applicable) before a PR can be merged to master, and code +quality and test coverage must not be changed by a significant margin. For some +repositories, visual screenshot-based tests are also included, and video +recordings of end-to-end tests are stored for later review. + +[You can read more about our continous integration efforts here](/development/continous-integration.md) + +## Releases + +Releases are made automatically based on the type of commits which have been +merged (major.minor.patch). Releases are automatically pushed to NPM. Release +notes are automatically generated. Users can subscribe to GitHub and NPM +releases. + +We host development, staging, and production environments for the Progressive +Web Application version of the OHIF Viewer. [Development][ohif-dev] always +reflects the latest changes on our master branch. [Staging][ohif-stage] is used +to regression test a release before a bi-weekly deploy to our [Production +environment][ohif-prod]. + +Important announcements are made on GitHub, tagged as Announcement, and pinned +so that they remain at the top of the Issue page. + +The Core team occasionally performs full manual testing to begin the process of +releasing a Stable version. Once testing is complete, the known issues are +addressed and a Stable version is released. + + + + +[groom-backlog]: https://www.agilealliance.org/glossary/backlog-grooming +[retrospective]: https://www.atlassian.com/team-playbook/plays/retrospective +[gh-issues]: https://github.com/OHIF/Viewers/issues/new/choose +[gh-labels]: https://github.com/OHIF/Viewers/labels + +[label-story]: https://github.com/OHIF/Viewers/labels/Story%20%3Araised_hands%3A +[label-tooling]: https://github.com/OHIF/Viewers/labels/Task%3A%20CI%2FTooling%20%3Arobot%3A +[label-docs]: https://github.com/OHIF/Viewers/labels/Task%3A%20Docs%20%3Abook%3A +[label-refactor]: https://github.com/OHIF/Viewers/labels/Task%3A%20Refactor%20%3Ahammer_and_wrench%3A +[label-tests]: https://github.com/OHIF/Viewers/labels/Task%3A%20Tests%20%3Amicroscope%3A +[label-bug]: https://github.com/OHIF/Viewers/labels/Bug%3A%20Verified%20%3Abug%3A + +[draft]: https://github.com/OHIF/Viewers/labels/PR%3A%20Draft +[awaiting-response]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Response%20%3Aspeech_balloon%3A +[awaiting-review]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Review%20%3Aeyes%3A +[awaiting-stories]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20UX%20Stories%20%3Adancer%3A +[awaiting-revisions]: https://github.com/OHIF/Viewers/labels/PR%3A%20Awaiting%20Revisions%20%3Apen%3A +[no-ux-impact]: https://github.com/OHIF/Viewers/labels/PR%3A%20No%20UX%20Impact%20%3Aupside_down_face%3A + +[ohif-dev]: https://viewer-dev.ohif.org +[ohif-stage]: https://viewer-stage.ohif.org +[ohif-prod]: https://viewer.ohif.org +[gh-board]: https://github.com/OHIF/Viewers/projects/4 +[label-grabs]: https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs+%3Araising_hand_woman%3A%22 +[contributing-docs]: ./development/contributing.md + diff --git a/platform/docs/versioned_docs/version-3.0/development/testing.md b/platform/docs/versioned_docs/version-3.0/development/testing.md new file mode 100644 index 00000000000..c7c846aaa3d --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/development/testing.md @@ -0,0 +1,217 @@ +--- +sidebar_position: 6 +sidebar_label: Testing +--- + +# Running Tests for OHIF + +We introduce here various test types that is available for OHIF, and how to run +each test in order to make sure your contribution hasn't broken any existing +functionalities. Idea and philosophy of each testing category is discussed in +the second part of this page. + +## Unit test + +To run the unit test: + +```bash +yarn run test:unit:ci +``` + +Note: You should have already installed all the packages with `yarn install`. + +Running unit test will generate a report at the end showing the successful and +unsuccessful tests with detailed explanations. + +## End-to-end test +For running the OHIF e2e test you need to run the following steps: + +- Open a new terminal, and from the root of the OHIF mono repo, run the following command: + + ```bash + yarn test:data + ``` + + This will download the required data to run the e2e tests (it might take a while). + The `test:data` only needs to be run once and checks the data out. Read more about + test data [below](#test-data). + +- Run the viewer with e2e config + + ```bash + APP_CONFIG=config/e2e.js yarn start + ``` + + You should be able to see test studies in the study list + + ![OHIF-e2e-test-studies](../assets/img/OHIF-e2e-test-studies.png) + +- Open a new terminal inside the OHIF project, and run the e2e cypress test + + ```bash + yarn test:e2e + ``` + + You should be able to see the cypress window open + + ![e2e-cypress](../assets/img/e2e-cypress.png) + + Run the tests by clicking on the `Run #number integration tests` . + + A new window will open, and you will see e2e tests being executed one after + each other. + + ![e2e-cypress-final](../assets/img/e2e-cypress-final.png) + + ## Test Data + The testing data is stored in two OHIF repositories. The first contains the + binary DICOM data, at [viewer-testdata](https://github.com/OHIF/viewer-testdata.git) + while the second module contains data in the DICOMweb format, installed as a submodule + into OHIF in the `testdata` directory. This is retrieved via the command + ```bash + yarn test:data + ``` + or the equivalent command `git submodule update --init` + When adding new data, run: + ``` + npm install -g dicomp10-to-dicomweb + mkdicomweb -d dicomweb dcm + ``` + to update the local dicomweb submodule in viewer-testdata. Then, commit + that data and update the submodules used in OHIF and in the viewer-testdata + parent modules. + + All data MUST be fully anonymized and allowed to be used for open access. + Any attributions should be included in the DCM directory. + +## Testing Philosophy + +> Testing is an opinionated topic. Here is a rough overview of our testing +> philosophy. See something you want to discuss or think should be changed? Open +> a PR and let's discuss. + +You're an engineer. You know how to write code, and writing tests isn't all that +different. But do you know why we write tests? Do you know when to write one, or +what kind of test to write? How do you know if a test is a _"good"_ test? This +document's goal is to give you the tools you need to make those determinations. + +Okay. So why do we write tests? To increase our... **CONFIDENCE** + +- If I do a large refactor, does everything still work? +- If I changed some critical piece of code, is it safe to push to production? + +Gaining the confidence we need to answer these questions after every change is +costly. Good tests allow us to answer them without manual regression testing. +What and how we choose to test to increase that confidence is nuanced. + +## Further Reading: Kinds of Tests + +Test's buy us confidence, but not all tests are created equal. Each kind of test +has a different cost to write and maintain. An expensive test is worth it if it +gives us confidence that a payment is processed, but it may not be the best +choice for asserting an element's border color. + +| Test Type | Example | Speed | Cost | +| ----------- | ------------------------------------------------------------------------ | ---------------- | ------------------------------------------------------------------------ | +| Static | `addNums(1, '2')` called with `string`, expected `int`. | :rocket: Instant | :money_with_wings: | +| Unit | `addNums(1, 2)` returns expected result `3` | :airplane: Fast | :money_with_wings::money_with_wings: | +| Integration | Clicking "Sign In", navigates to the dashboard (mocked network requests) | :running: Okay | :money_with_wings::money_with_wings::money_with_wings: | +| End-to-end | Clicking "Sign In", navigates to the dashboard (no mocks) | :turtle: Slow | :money_with_wings::money_with_wings::money_with_wings::money_with_wings: | + +- :rocket: Speed: How quickly tests run +- :money_with_wings: Cost: Time to write, and to debug when broken (more points + of failure) + +### Static Code Analysis + +Modern tooling gives us this "for free". It can catch invalid regular +expressions, unused variables, and guarantee we're calling methods/functions +with the expected parameter types. + +Example Tooling: + +- [ESLint][eslint-rules] +- [TypeScript][typescript-docs] or [Flow][flow-org] + +### Unit Tests + +The building blocks of our libraries and applications. For these, you'll often +be testing a single function or method. Conceptually, this equates to: + +_Pure Function Test:_ + +- If I call `sum(2, 2)`, I expect the output to be `4` + +_Side Effect Test:_ + +- If I call `resetViewport(viewport)`, I expect `cornerstone.reset` to be called + with `viewport` + +#### When to use + +Anything that is exposed as public API should have unit tests. + +#### When to avoid + +You're actually testing implementation details. You're testing implementation +details if: + +- Your test does something that the consumer of your code would never do. + - IE. Using a private function +- A refactor can break your tests + +### Integration Tests + +We write integration tests to gain confidence that several units work together. +Generally, we want to mock as little as possible for these tests. In practice, +this means only mocking network requests. + +### End-to-End Tests + +These are the most expensive tests to write and maintain. Largely because, when +they fail, they have the largest number of potential points of failure. So why +do we write them? Because they also buy us the most confidence. + +#### When to use + +Mission critical features and functionality, or to cover a large breadth of +functionality until unit tests catch up. Unsure if we should have a test for +feature `X` or scenario `Y`? Open an issue and let's discuss. + +### General + +- [Assert(js) Conf 2018 Talks][assert-js-talks] + - [Write tests. Not too many. Mostly integration.][kent-talk] - Kent C. Dodds + - [I see your point, but…][gleb-talk] - Gleb Bahmutov +- [Static vs Unit vs Integration vs E2E Testing][kent-blog] - Kent C. Dodds + (Blog) + +### End-to-end Testing w/ Cypress + +- [Getting Started](https://docs.cypress.io/guides/overview/why-cypress.html) + - Be sure to check out `Getting Started` and `Core Concepts` +- [Best Practices](https://docs.cypress.io/guides/references/best-practices.html) +- [Example Recipes](https://docs.cypress.io/examples/examples/recipes.html) + + + + +[eslint-rules]: https://eslint.org/docs/rules/ +[mini-pacs]: https://github.com/OHIF/viewer-testdata +[typescript-docs]: https://www.typescriptlang.org/docs/home.html +[flow-org]: https://flow.org/ + +[assert-js-talks]: https://www.youtube.com/playlist?list=PLZ66c9_z3umNSrKSb5cmpxdXZcIPNvKGw +[kent-talk]: https://www.youtube.com/watch?v=Fha2bVoC8SE +[gleb-talk]: https://www.youtube.com/watch?v=5FnalKRjpZk +[kent-blog]: https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests + +[testing-trophy]: https://twitter.com/kentcdodds/status/960723172591992832?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E960723172591992832&ref_url=https%3A%2F%2Fkentcdodds.com%2Fblog%2Fwrite-tests +[aaron-square]: https://twitter.com/Carofine247/status/966727489274961920 +[gleb-pyramid]: https://twitter.com/Carofine247/status/966764532046684160/photo/3 +[testing-pyramid]: https://dojo.ministryoftesting.com/dojo/lessons/the-mobile-test-pyramid +[testing-dorito]: https://twitter.com/denvercoder/status/960752578198843392 +[testing-dorito-img]: https://pbs.twimg.com/media/DVVHXycUMAAcN-F?format=jpg&name=4096x4096 + diff --git a/platform/docs/versioned_docs/version-3.0/faq.md b/platform/docs/versioned_docs/version-3.0/faq.md new file mode 100644 index 00000000000..67b7d30b2e8 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/faq.md @@ -0,0 +1,81 @@ +--- +sidebar_position: 8 +sidebar_label: FAQ +--- + +# Frequently Asked Questions + +## Index + +- [Report a bug][report-bug] +- [Request a feature][new-feature] +- [Commercial Support & Consulting][commercial-support] +- [Academic collaborations][academic] +- [FDA Clearance or CE Marking][fda-clearance] +- [HIPAA Compliance][hipaa] + +### How do I report a bug? + +Navigate to our [GitHub Repository][new-issue], and submit a new bug report. +Follow the steps outlined in the [Bug Report Template][bug-report-template]. + +### How can I request a new feature? + +At the moment we are in the process of defining our roadmap and will do our best +to communicate this to the community. If your requested feature is on the +roadmap, then it will most likely be built at some point. If it is not, you are +welcome to build it yourself and [contribute it](development/contributing.md). +If you have resources and would like to fund the development of a feature, +please [contact us](https://www.ohif.org) or work with community members that +offer [consulting services][commercial-support]. + +### Who should I contact about Academic Collaborations? + +[Gordon J. Harris](https://www.dfhcc.harvard.edu/insider/member-detail/member/gordon-j-harris-phd/) +at Massachusetts General Hospital is the primary contact for any academic +collaborators. We are always happy to hear about new groups interested in using +the OHIF framework, and may be able to provide development support if the +proposed collaboration has an impact on cancer research. + +### Does OHIF offer commercial support? + +The Open Health Imaging Foundation does not offer commercial support, however, +some community members do offer consulting services. You can search our +[Community Forum](https://community.ohif.org/) for more information. + +### Does The OHIF Viewer have [510(k) Clearance][501k-clearance] from the U.S. F.D.A or [CE Marking][ce-marking] from the European Commission? + +**NO.** The OHIF Viewer is **NOT** F.D.A. cleared or CE Marked. It is the users' +responsibility to ensure compliance with applicable rules and regulations. The +[License](https://github.com/OHIF/Viewers/blob/master/LICENSE) for the OHIF +Platform does not prevent your company or group from seeking F.D.A. clearance +for a product built using the platform. + +If you have gone this route (or are going there), please let us know because we +would be interested to hear about your experience. + +### Is The OHIF Viewer [HIPAA][hipaa-def] Compliant? + +**NO.** The OHIF Viewer **DOES NOT** fulfill all of the criteria to become HIPAA +Compliant. It is the users' responsibility to ensure compliance with applicable +rules and regulations. + + + + + +[report-bug]: #how-do-i-report-a-bug +[new-feature]: #how-can-i-request-a-new-feature +[commercial-support]: #does-ohif-offer-commercial-support +[academic]: #who-should-i-contact-about-academic-collaborations +[fda-clearance]: #does-the-ohif-viewer-have-510k-clearance-from-the-us-fda-or-ce-marking-from-the-european-commission +[hipaa]: #is-the-ohif-viewer-hipaa-compliant + +[501k-clearance]: https://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance/HowtoMarketYourDevice/PremarketSubmissions/PremarketNotification510k/ +[ce-marking]: https://ec.europa.eu/growth/single-market/ce-marking_en +[hipaa-def]: https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act +[new-issue]: https://github.com/OHIF/Viewers/issues/new/choose +[bug-report-template]: https://github.com/OHIF/Viewers/issues/new?assignees=&labels=Bug+Report+%3Abug%3A&template=---bug-report.md&title= + diff --git a/platform/docs/versioned_docs/version-3.0/platform/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/_category_.json new file mode 100644 index 00000000000..842e4abf4a1 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Platform", + "position": 6 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/browser-support.md b/platform/docs/versioned_docs/version-3.0/platform/browser-support.md new file mode 100644 index 00000000000..f717432aedc --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/browser-support.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 2 +--- +# Browser Support + +The browsers that we support are specified in the `.browserlistrc` file located +in the `platform/viewer` project. While we leverage the latest language features +when writing code, we rely on `babel` to _transpile_ our code so that it can run +in the browsers that we support. + +## In Practice + +The OHIF Viewer is capable of _running_ on: + +- IE 11 +- FireFox +- Chrome +- Safari +- Edge + +However, we do not have the resources to adequately test and maintain bug free +functionality across all of these. In order to push web based medical imaging +forward, we focus our development efforts on recent version of modern evergreen +browsers. + +Our support of older browsers equates to our willingness to review PRs for bug +fixes, and target their minimum JS support whenever possible. + +### Polyfills + +> A polyfill, or polyfiller, is a piece of code (or plugin) that provides the +> technology that you, the developer, expect the browser to provide natively. + +An example of a polyfill is that you expect `Array.prototype.filter` to exist, +but for some reason, the browser that's being used has not implemented that +language feature yet. Our earlier transpilation will rectify _syntax_ +discrepancies, but unimplemented features require a "temporary" implementation. +That's where polyfills step in. + +You can utilize a service like [polyfill.io](https://polyfill.io/v3/) to +auto-detect and apply polyfills as needed, or you can update the PWA build to +include polyfill's in your bundle by incorporating [core-js][core-js] + + + + +[core-js]: https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md + diff --git a/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md b/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md new file mode 100644 index 00000000000..4fd2691a4aa --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/environment-variables.md @@ -0,0 +1,27 @@ +--- +sidebar_position: 3 +sidebar_label: Environment Variables +--- +# Environment Variables + +There are a number of environment variables we use at build time to influence the output application's behavior. + +```bash +# Application +NODE_ENV=< production | development > +DEBUG=< true | false > +APP_CONFIG=< relative path to application configuration file > +PUBLIC_URL=<> +VERSION_NUMBER= +BUILD_NUM= +# i18n +USE_LOCIZE= +LOCIZE_PROJECTID= +LOCIZE_API_KEY= +``` + +## Setting Environment Variables + +- `npx cross-env` +- `.env` files +- env variables on build machine, or for terminal session diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json new file mode 100644 index 00000000000..b7a30d960fb --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Extensions", + "position": 9 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md new file mode 100644 index 00000000000..148b82a9faf --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/extension.md @@ -0,0 +1,55 @@ +--- +sidebar_position: 4 +sidebar_label: Extension Manager +--- + +# Extension Manager + +## Overview + +The `ExtensionManager` is a class made available to us via the `@ohif/core` +project (platform/core). Our application instantiates a single instance of it, +and provides a `ServicesManager` and `CommandsManager` along with the +application's configuration through the appConfig key (optional). + +```js +const commandsManager = new CommandsManager(); +const servicesManager = new ServicesManager(); +const extensionManager = new ExtensionManager({ + commandsManager, + servicesManager, + appConfig, +}); +``` + +The `ExtensionManager` only has a few public members: + +- `setActiveDataSource` - Sets the active data source for the application +- `getDataSources` - Returns the registered data sources +- `getActiveDataSource` - Returns the currently active data source +- `getModuleEntry` - Returns the module entry by the give id. + +## Accessing Modules + +We use `getModuleEntry` in our `ViewerLayout` logic to find the panels based on +the provided IDs in the mode's configuration. + +For instance: +`extensionManager.getModuleEntry("@ohif/extension-measurement-tracking.panelModule.seriesList")` +accesses the `seriesList` panel from `panelModule` of the +`@ohif/extension-measurement-tracking` extension. + +```js +const getPanelData = id => { + const entry = extensionManager.getModuleEntry(id); + const content = entry.component; + + return { + iconName: entry.iconName, + iconLabel: entry.iconLabel, + label: entry.label, + name: entry.name, + content, + }; +}; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md new file mode 100644 index 00000000000..b1920b52104 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/index.md @@ -0,0 +1,325 @@ +--- +sidebar_position: 1 +sidebar_label: Introduction +--- + +# Introduction + +We have re-designed the architecture of the `OHIF-v3` to enable building +applications that are easily extensible to various use cases (modes) that behind +the scene would utilize desired functionalities (extensions) to reach the goal +of the use case. + +Previously, extensions were “additive” and could not easily be mixed and matched +within the same viewer for different use cases. Previous `OHIF-v2` architecture +meant that any minor extension alteration usually would require the user to hard +fork. E.g. removing some tools from the toolbar of the cornerstone +extension meant you had to hard fork it, which was frustrating if the +implementation was otherwise the same as master. + +> - Developers should make packages of _reusable_ functionality as extensions, +> and can consume publicly available extensions. +> - Any conceivable radiological workflow or viewer setup will be able to be +> built with the platform through _modes_. + +Practical examples of extensions include: + +- A set of segmentation tools that build on top of the `cornerstone` viewport +- A set of rendering functionalities to volume render the data +- [See our maintained extensions for more examples of what's possible](#maintained-extensions) + +**Diagram showing how extensions are configured and accessed.** + + + +## Extension Skeleton + +An extension is a plain JavaScript object that has `id` and `version` properties, and one or +more [modules](#modules) and/or [lifecycle hooks](#lifecycle-hooks). + +```js +// prettier-ignore +export default { + /** + * Required properties. Should be a unique value across all extensions. + */ + id, + + // Lifecyle + preRegistration() { /* */ }, + onModeEnter() { /* */ }, + onModeExit() { /* */ }, + // Modules + getLayoutTemplateModule() { /* */ }, + getDataSourcesModule() { /* */ }, + getSopClassHandlerModule() { /* */ }, + getPanelModule() { /* */ }, + getViewportModule() { /* */ }, + getCommandsModule() { /* */ }, + getContextModule() { /* */ }, + getToolbarModule() { /* */ }, + getHangingProtocolModule() { /* */ }, +} +``` + +## OHIF-Maintained Extensions + +A small number of powerful extensions for popular use cases are maintained by +OHIF. They're co-located in the [`OHIF/Viewers`][viewers-repo] repository, in +the top level [`extensions/`][ext-source] directory. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionDescriptionModules
+ + Default + + + Default extension provides default viewer layout, a study/series + browser, and a datasource that maps to a DICOMWeb compliant backend + commandsModule, ContextModule, DataSourceModule, HangingProtocolModule, LayoutTemplateModule, PanelModule, SOPClassHandlerModule, ToolbarModule
+ + Cornerstone + + + Provides rendering functionalities for 2D images. + ViewportModule, CommandsModule
+ DICOM PDF + + Renders PDFs for a specific SopClassUID. + Viewport, SopClassHandler
+ DICOM SR + + Maintained extensions for cornerstone and visualization of DICOM Structured Reports + ViewportModule, CommandsModule, SOPClassHandlerModule
+ Measurement tracking + + Tracking measurements in the measurement panel + ContextModule,PanelModule,ViewportModule,CommandsModule
+ +## Registering of Extensions + +`viewer` starts by registering all the extensions specified inside the +`pluginConfig.json`, by default we register all extensions in the repo. + + +```js title=platform/viewer/pluginConfig.json +// Simplified version of the `pluginConfig.json` file +{ + "extensions": [ + { + "packageName": "@ohif/extension-cornerstone", + "version": "3.0.0" + }, + { + "packageName": "@ohif/extension-measurement-tracking", + "version": "3.0.0" + }, + // ... + ], + "modes": [ + { + "packageName": "@ohif/mode-longitudinal", + "version": "0.0.1" + } + ] +} +``` + +:::note Important +You SHOULD NOT directly register extensions in the `pluginConfig.json` file. +Use the provided `cli` to add/remove/install/uninstall extensions. Read more [here](../../development/ohif-cli.md) +::: + +The final registration and import of the extensions happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). + +After an extension gets registered withing the `viewer`, +each [module](#modules) defined by the extension becomes available to the modes +via the `ExtensionManager` by requesting it via its id. +[Read more about Extension Manager](#extension-manager) + +## Lifecycle Hooks + +Currently, there are three lifecycle hook for extensions: + +[`preRegistration`](./lifecycle/#preRegistration) This hook is called once on +initialization of the entire viewer application, used to initialize the +extensions state, and consume user defined extension configuration. If an +extension defines the [`preRegistration`](./lifecycle/#preRegistration) +lifecycle hook, it is called before any modules are registered in the +`ExtensionManager`. It's most commonly used to wire up extensions to +[services](./../services/index.md) and [commands](./modules/commands.md), and to +bootstrap 3rd party libraries. + +[`onModeEnter`](./lifecycle#onModeEnter): This hook is called whenever a new +mode is entered, or a mode’s data or datasource is switched. This hook can be +used to initialize data. + +[`onModeExit`](./lifecycle#onModeExit): Similarly to onModeEnter, this hook is +called when navigating away from a mode, or before a mode’s data or datasource +is changed. This can be used to clean up data (e.g. remove annotations that do +not need to be persisted) + +## Modules + +Modules are the meat of extensions, the `blocks` that we have been talking about +a lot. They provide "definitions", components, and filtering/mapping logic that +are then made available to modes and services. + +Each module type has a special purpose, and is consumed by our viewer +differently. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Types + Description
+ + LayoutTemplate (NEW) + + Control Layout of a route
+ + DataSource (NEW) + + Control the mapping from DICOM metadata to OHIF-metadata
+ + SOPClassHandler + + Determines how retrieved study data is split into "DisplaySets"
+ + Panel + + Adds left or right hand side panels
+ + Viewport + + Adds a component responsible for rendering a "DisplaySet"
+ + Commands + + Adds named commands, scoped to a context, to the CommandsManager
+ + Toolbar + + Adds buttons or custom components to the toolbar
+ + Context + + Shared state for a workflow or set of extension module definitions
+ + HangingProtocol + + Adds hanging protocol rules
+ +Tbl. Module types +with abridged descriptions and examples. Each module links to a dedicated +documentation page. + +### Contexts + +The `@ohif/viewer` tracks "active contexts" that extensions can use to scope +their functionality. Some example contexts being: + +- Route: `ROUTE:VIEWER`, `ROUTE:STUDY_LIST` +- Active Viewport: `ACTIVE_VIEWPORT:CORNERSTONE`, `ACTIVE_VIEWPORT:VTK` + +An extension module can use these to say "Only show this Toolbar Button if the +active viewport is a Cornerstone viewport." This helps us use the appropriate UI +and behaviors depending on the current contexts. + +For example, if we have hotkey that "rotates the active viewport", each Viewport +module that supports this behavior can add a command with the same name, scoped +to the appropriate context. When the `command` is fired, the "active contexts" +are used to determine the appropriate implementation of the rotation behavior. + + + + +[viewers-repo]: https://github.com/OHIF/Viewers +[ext-source]: https://github.com/OHIF/Viewers/tree/master/extensions +[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js + diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md new file mode 100644 index 00000000000..2e5fb81c2a1 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/installation.md @@ -0,0 +1,12 @@ +--- +sidebar_position: 5 +sidebar_label: Installation +--- + +# Extension: Installation + +OHIF-v3 provides the ability to utilize external extensions. + + +You can use ohif `cli` tool to install both local and publicly published +extensions on NPM. You can read more [here](../../development/ohif-cli.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md new file mode 100644 index 00000000000..422bce794cd --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/lifecycle.md @@ -0,0 +1,128 @@ +--- +sidebar_position: 3 +sidebar_label: Lifecycle Hooks +--- + +# Extensions: Lifecycle Hooks + +## Overview + +Extensions can implement specific lifecycle methods. + +- preRegistration +- onModeEnter +- onModeExit + +## preRegistration + +If an extension defines the `preRegistration` lifecycle hook, it is called +before any modules are registered in the `ExtensionManager`. This hook can be +used to: + +- initialize 3rd party libraries +- register event listeners +- add or call services +- add or call commands + +The `preRegistration` hook receives an object containing the +`ExtensionManager`'s associated `ServicesManager`, `CommandsManager`, and any +`configuration` that was provided with the extension at time of registration. + +Example `preRegistration` implementation that register a new service and make it +available in the app. We will talk more in details for creating a new service +for `OHIF-v3`. + +```js +// new service inside new extension +import MyNewService from './MyNewService'; + +export default function MyNewServiceWithServices(serviceManager) { + return { + name: 'MyNewService', + create: ({ configuration = {} }) => { + return new MyNewService(serviceManager); + }, + }; +} +``` + +and + +```js +import MyNewService from './MyNewService' + +export default { + id, + + /** + * @param {object} params + * @param {object} params.configuration + * @param {ServicesManager} params.servicesManager + * @param {CommandsManager} params.commandsManager + * @returns void + */ + preRegistration({ servicesManager, commandsManager, configuration }) { + console.log('Wiring up important stuff.'); + + window.importantStuff = () => { + console.log(configuration); + }; + + console.log('Important stuff has been wired.'); + window.importantStuff(); + + // Registering new services + servicesManager.registerService(MyNewService(servicesManager)); + }, + }, +}; +``` + +## onModeEnter + +If an extension defines the `onModeEnter` lifecycle hook, it is called when a +new mode is enters, or a mode's data or datasource is switched. + +For instance, in DICOM structured report extension (`dicom-sr`), we are using +`onModeEnter` to re-create the displaySets after a new mode is entered. + +_Example `onModeEnter` hook implementation_ + +```js +export default { + id: '@ohif/extension-cornerstone-dicom-sr', + + onModeEnter({ servicesManager }) { + const { DisplaySetService } = servicesManager.services; + const displaySetCache = DisplaySetService.getDisplaySetCache(); + + const srDisplaySets = displaySetCache.filter( + ds => ds.SOPClassHandlerId === SOPClassHandlerId + ); + + srDisplaySets.forEach(ds => { + // New mode route, allow SRs to be hydrated again + ds.isHydrated = false; + }); + }, +}; +``` + +## onModeExit + +If an extension defines the `onModeExit` lifecycle hook, it is called when +navigating away from a mode. This hook can be used to clean up data tasks such +as unregistering services, removing annotations that do not need to be +persisted. + +_Example `onModeExit` hook implementation_ + +```js +export default { + id: 'myExampleExtension', + + onModeExit({ servicesManager, commandsManager }) { + myCacheService.purge(); + }, +}; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json new file mode 100644 index 00000000000..c131ccdd7e3 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Modules", + "position": 3 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md new file mode 100644 index 00000000000..b37202bf61e --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/commands.md @@ -0,0 +1,121 @@ +--- +sidebar_position: 2 +sidebar_label: Commands +--- +# Module: Commands + + +## Overview +`CommandsModule` includes list of arbitrary functions. These may activate tools, communicate with a server, open a modal, etc. +The significant difference between `OHIF-v3` and `OHIF-v2` is that in `v3` a `mode` defines +its toolbar, and which commands each tool call is inside in its toolDefinition + +An extension can register a Commands Module by defining a `getCommandsModule` +method. The Commands Module allows us to register one or more commands scoped to +specific [contexts](./../index.md#contexts). Commands have several unique +characteristics that make them tremendously powerful: + +- Multiple implementations for the same command can be defined +- Only the correct command's implementation will be run, dependent on the + application's "context" +- Commands are used by hotkeys, toolbar buttons and render settings + +Here is a simple example commands module: + +```js +const getCommandsModule = () => ({ + definitions: { + exampleActionDef: { + commandFn: ({ param1 }) => { + console.log(`param1's value is: ${param1}`); + }, + // storeContexts: ['viewports'], + options: { param1: 'param1' }, + context: 'VIEWER', // optional + }, + }, + defaultContext: 'ACTIVE_VIEWPORT::DICOMSR', +}); +``` + + +Each definition returned by the Commands Module is registered to the +`ExtensionManager`'s `CommandsManager`. + +> `storeContexts` has been removed in `OHIF-v3` and now modules have access to all commands and services. This change enables support for user-registered services. + +## Command Definitions + +The command definition consists of a named command (`exampleActionDef` below) and a +`commandFn`. The command name is used to call the command, and the `commandFn` +is the "command" that is actioned. + +```js +exampleActionDef: { + commandFn: ({ param1, options }) => { }, + options: { param1: 'measurement' }, + context: 'DEFAULT', +} +``` + +| Property | Type | Description | +| --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| `commandFn` | func | The function to call when command is run. Receives `options` and `storeContexts`. | +| `options` | object | (optional) Arguments to pass at the time of calling to the `commandFn` | +| `context` | string[] or string | (optional) Overrides the `defaultContext`. Let's us know if command is currently "available" to be run. | + +## Command Behavior + + + +**If there are multiple valid commands for the application's active contexts** + +- What happens: all commands are run +- When to use: A `clearData` command that cleans up state for multiple + extensions + +**If no commands are valid for the application's active contexts** + +- What happens: a warning is printed to the console +- When to use: a `hotkey` (like "invert") that doesn't make sense for the + current viewport (PDF or HTML) + +## `CommandsManager` Public API + +If you would like to run a command in the consuming app or an extension, you can +use `CommandsManager.runCommand(commandName, options = {}, contextName)` + + +```js +// Returns all commands for a given context +commandsManager.getContext('string'); + +// Run a command, it will run all the `speak` commands in all contexts +commandsManager.runCommand('speak', { command: 'hello' }); + +// Run command, from Default context +commandsManager.runCommand('speak', { command: 'hello' }, ['DEFAULT']); +``` + +The `ExtensionManager` handles registering commands and creating contexts, so +most consumer's won't need these methods. If you find yourself using these, ask +yourself "why can't I register these commands via an extension?" + +```js +// Used by the `ExtensionManager` to register new commands +commandsManager.registerCommand('context', 'name', commandDefinition); + +// Creates a new context; clears the context if it already exists +commandsManager.createContext('string'); +``` + +### Contexts + +It is up to the consuming application to define what contexts are possible, and +which ones are currently active. As extensions depend heavily on these, we will +likely publish guidance around creating contexts, and ways to override extension +defined contexts in the near future. If you would like to discuss potential +changes to how contexts work, please don't hesitate to create a new GitHub +issue. + +[Some additional information on Contexts can be found here.](./../index.md#contexts) diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md new file mode 100644 index 00000000000..35de381a3da --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/contextModule.md @@ -0,0 +1,30 @@ +--- +sidebar_position: 9 +sidebar_label: Context +--- +# Module: Context + +## Overview +This new module type allows you to connect components via a shared context. You can create a context that two components, e.g. a viewport and a panel can use to synchronize and communicate. An extensive example of this can be seen in the longitudinal mode’s custom extensions. + + + +```jsx +const ExampleContext = React.createContext(); + +function ExampleContextProvider({ children }) { + return ( + + {children} + + ); +} + +const getContextModule = () => [ + { + name: 'ExampleContext', + context: ExampleContext, + provider: ExampleContextProvider, + }, +]; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md new file mode 100644 index 00000000000..381d7ea7e1e --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/data-source.md @@ -0,0 +1,106 @@ +--- +sidebar_position: 3 +sidebar_label: Data Source +--- + +# Module: Data Source + +## Overview + +The internal data structure of OHIF’s metadata follows naturalized DICOM JSON, A +format pioneered by `dcmjs`. In short DICOM metadata headers with DICOM Keywords +instead of tags and sequences as arrays, for easy development and clear code. + +We have built a standard for fetching and mapping data into OHIF’s native +format, which we call DataSources, and have provided one implementation of this +standard. + +You can make another datasource implementation which communicates to your +backend and maps to OHIF’s native format, then use any existing mode on your +platform. Your data doesn’t even need to be DICOM if you can map some +proprietary data to the correct format. + +The DataSource is also a place to add easy helper methods that platform-specific +extensions can call in order to interact with the backend, meaning proprietary +data interactions can be wrapped in extensions. + +```js +const getDataSourcesModule = () => [ + { + name: 'exampleDataSource', + type: 'webApi', // 'webApi' | 'local' | 'other' + createDataSource: dataSourceConfig => { + return IWebApiDataSource.create(/* */); + }, + }, +]; +``` + +Default extension provides two main data sources that are commonly used: +`dicomweb` and `dicomjson` + +```js +import { createDicomWebApi } from './DicomWebDataSource/index.js'; +import { createDicomJSONApi } from './DicomJSONDataSource/index.js'; + +function getDataSourcesModule() { + return [ + { + name: 'dicomweb', + type: 'webApi', + createDataSource: createDicomWebApi, + }, + { + name: 'dicomjson', + type: 'jsonApi', + createDataSource: createDicomJSONApi, + }, + ]; +} +``` + +## Custom DataSource + +You can add your custom datasource by creating the implementation using +`IWebApiDataSource.create` from `@ohif/core`. This factory function creates a +new "Web API" data source that fetches data over HTTP. + +You need to make sure, you implement the following functions for the data +source. + +```js title="platform/core/src/DataSources/IWebApiDataSource.js" +function create({ + query, + retrieve, + store, + reject, + parseRouteParams, + deleteStudyMetadataPromise, + getImageIdsForDisplaySet, + getImageIdsForInstance, +}) { + /* */ +} +``` + +You can take a look at `dicomweb` data source implementation to get an idea +`extensions/default/src/DicomWebDataSource/index.js` + +## Static WADO Client + +If the configuration for the data source has the value staticWado set, then it +is assumed that queries for the studies return a super-set of the studies, as it +is assumed to be returning a static list. The StaticWadoClient performs the +search functionality manually, by interpreting the query parameters and then +applying them to the returned response. This functionality may be useful for +other types of DICOMweb back ends, where they are capable of performing queries, +but don't allow for querying certain types of fields. However, that only works +as long as the size of the studies list isn't too large that client side +selection isn't too expensive. + +## DicomMetadataStore + +In `OHIF-v3` we have a central location for the metadata of studies, and they are +located in `DicomMetadataStore`. Your custom datasource can communicate with +`DicomMetadataStore` to store, and fetch Study/Series/Instance metadata. We will +learn more about `DicomMetadataStore` in services. diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md new file mode 100644 index 00000000000..649d6644eec --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/hpModule.md @@ -0,0 +1,58 @@ +--- +sidebar_position: 8 +sidebar_label: Hanging Protocol +--- +# Module: Hanging Protocol + +## Overview +`hangingProtocolModule` provides the protocols for hanging the displaySets in the viewer. +This module can be as simple as loading a list of pre-defined protocols, or it can be more complex +and `fetch` the protocols from a server. + +You can read more about hanging protocols in HangingProtocolService. + +```js +const deafultProtocol = { + id: 'defaultProtocol', + locked: true, + hasUpdatedPriorsInformation: false, + name: 'Default', + createdDate: '2021-02-23T19:22:08.894Z', + modifiedDate: '2021-02-23T19:22:08.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + stages: [ + { + id: 'nwzau7jDkEkL8djfr', + name: 'oneByOne', + viewportStructure: { + type: 'grid', + properties: { + rows: 1, + columns: 1, + }, + }, + viewports: [ + { + viewportSettings: [], + imageMatchingRules: [], + seriesMatchingRules: [], + studyMatchingRules: [], + }, + ], + createdDate: '2021-02-23T19:22:08.894Z', + }, + ], + numberOfPriorsReferenced: -1, +}; + +function getHangingProtocolModule() { + return [ + { + name: hangingProtocolName, + protocols: [deafultProtocol], + }, + ]; +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md new file mode 100644 index 00000000000..66c839dae1b --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/layout-template.md @@ -0,0 +1,139 @@ +--- +sidebar_position: 7 +sidebar_label: Layout Template +--- + +# Module: Layout Template + +## Overview + +`LayoutTemplates` are a new concept in v3 that modes use to control the layout +of a route. A layout template is a React component that is given a set of +managers that define apis to access toolbar state, commands, and hotkeys, as +well as props defined by the layout template. + +For instance the default LayoutTemplate takes in leftPanels, rightPanels and +viewports as props, which it uses to build its view. + +In addition, `layout template` has complete control over the structure of the +application. You could have tools down the left side, or a strict guided +workflow with tools set programmatically, the choice is yours for your use case. + +```jsx +const getLayoutTemplateModule = (/* ... */) => [ + { + id: 'exampleLayout', + name: 'exampleLayout', + component: ExampleLayoutComponent, + }, +]; +``` + +The `props` that are passed to `layoutTemplate` are managers and service, along +with the defined mode left/right panels, mode's defined viewports and OHIF +`ViewportGridComp`. LayoutTemplate leverages extensionManager to grab typed +extension module entries: `*.getModuleEntry(id)` + +A simplified code for `Default extension`'s layout template is: + +```jsx title="extensions/default/src/ViewerLayout/index.jsx" +import React from 'react'; +import { SidePanel } from '@ohif/ui'; + +function Toolbar({ servicesManager }) { + const { ToolBarService } = servicesManager.services; + + return ( + <> + // ToolBarService.getButtonSection('primary') to get toolbarButtons + {toolbarButtons.map((toolDef, index) => { + const { id, Component, componentProps } = toolDef; + return ( + ToolBarService.recordInteraction(args)} + /> + ); + })} + + ); +} + +function ViewerLayout({ + // From Extension Module Params + extensionManager, + servicesManager, + hotkeysManager, + commandsManager, + // From Modes + leftPanels, + rightPanels, + viewports, + ViewportGridComp, +}) { + const getPanelData = id => { + const entry = extensionManager.getModuleEntry(id); + const content = entry.component; + + return { + iconName: entry.iconName, + iconLabel: entry.iconLabel, + label: entry.label, + name: entry.name, + content, + }; + }; + + const getViewportComponentData = viewportComponent => { + const entry = extensionManager.getModuleEntry(viewportComponent.namespace); + + return { + component: entry.component, + displaySetsToDisplay: viewportComponent.displaySetsToDisplay, + }; + }; + + const leftPanelComponents = leftPanels.map(getPanelData); + const rightPanelComponents = rightPanels.map(getPanelData); + const viewportComponents = viewports.map(getViewportComponentData); + + return ( +
+ + +
+ {/* LEFT SIDEPANELS */} + + + {/* TOOLBAR + GRID */} + + + {/* Rigth SIDEPANELS */} + +
+
+ ); +} +``` + +## Overview Video + +
+ +
diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md new file mode 100644 index 00000000000..1ad0d8c0813 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/panel.md @@ -0,0 +1,115 @@ +--- +sidebar_position: 6 +sidebar_label: Panel +--- + +# Module: Panel + +## Overview + +The default LayoutTemplate has panels on the left and right sides, however one +could make a template with panels at the top or bottom and make extensions with +panels intended for such slots. + +An extension can register a Panel Module by defining a `getPanelModule` method. +The panel module provides the ability to define `menuOptions` and `components` +that can be used by the consuming application. `components` are React Components +that can be displayed in the consuming application's "Panel" Component. + +![panel-module-v3](../../../assets/img/panel-module-v3.png) + +The `menuOptions`'s `target` key, points to a registered `components`'s `id`. A +`defaultContext` is applied to all `menuOption`s; however, each `menuOption` can +optionally provide its own `context` value. + +The `getPanelModule` receives an object containing the `ExtensionManager`'s +associated `ServicesManager` and `CommandsManager`. + +```jsx +import PanelMeasurementTable from './PanelMeasurementTable.js'; + +function getPanelModule({ + commandsManager, + extensionManager, + servicesManager, +}) { + const wrappedMeasurementPanel = () => { + return ( + + ); + }; + + return [ + { + name: 'measure', + iconName: 'list-bullets', + iconLabel: 'Measure', + label: 'Measurements', + isDisabled: studies => {}, // optional + component: wrappedMeasurementPanel, + }, + ]; +} +``` + +## Consuming Panels Inside Modes + +As explained earlier, extensions make the functionalities and components +available and `modes` utilize them to build an app. So, as seen above, we are +not actually defining which side the panel should be opened. Our extension is +providing the component with its. + +New: You can easily add multiple panels to the left/right side of the viewer +using the mode configuration. As seen below, the `leftPanels` and `rightPanels` +accept an `Array` of the `IDs`. + +```js + +const extensionDependencies = { + '@ohif/extension-default': '^3.0.0', + '@ohif/extension-cornerstone': '^3.0.0', + '@ohif/extension-measurement-tracking': '^3.0.0', + '@ohif/extension-cornerstone-dicom-sr': '^3.0.0', +}; + +const id = 'viewer' +const version = '3.0.0 + +function modeFactory({ modeConfiguration }) { + return { + id, + routes: [ + { + path: 'longitudinal', + layoutTemplate: ({ location, servicesManager }) => { + return { + id, + props: { + leftPanels: [ + '@ohif/extension-measurement-tracking.panelModule.seriesList', + ], + rightPanels: [ + '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', + ], + viewports, + }, + }; + }, + }, + ], + extensions: extensionDependencies + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +}; + +export default mode; + +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md new file mode 100644 index 00000000000..eb1b7a46410 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/sop-class-handler.md @@ -0,0 +1,108 @@ +--- +sidebar_position: 4 +sidebar_label: SOP Class Handler +--- +# Module: SOP Class Handler + +## Overview +This module defines how a specific DICOM SOP class should be processed to make a displaySet, something that can be hung in a viewport. An extension can register a [SOP Class][sop-class-link] Handler Module by defining a `getSopClassHandlerModule` method. The [SOP Class][sop-class-link]. + +The mode chooses what SOPClassHandlers to use, so you could process a series in a different way depending on mode within the same application. + + +SOPClassHandler is a bit different from the other modules, as it doesn't provide a `1:1` +schema for UI or provide its own components. It instead defines: + +- `sopClassUIDs`: an array of string SOP Class UIDs that the + `getDisplaySetFromSeries` method should be applied to. +- `getDisplaySetFromSeries`: a method that maps series and study metadata to a + display set + +A `displaySet` has the following shape: + +```js +return { + Modality: 'MR', + displaySetInstanceUIDD + SeriesDate, + SeriesTime, + SeriesInstanceUID, + StudyInstanceUID, + SeriesNumber, + FrameRate, + SeriesDescription, + isMultiFrame, + numImageFrames, + SOPClassHandlerId, +} +``` + +## Example SOP Class Handler Module + +```js +import ImageSet from '@ohif/core/src/classes/ImageSet'; + + +const sopClassDictionary = { + CTImageStorage: "1.2.840.10008.5.1.4.1.1.2", + MRImageStorage: "1.2.840.10008.5.1.4.1.1.4", +}; + + +// It is important to note that the used SOPClassUIDs in the modes are in the order that is specified in the array. +const sopClassUids = [ + sopClassDictionary.CTImageStorage, + sopClassDictionary.MRImageStorage, +; + +const makeDisplaySet = (instances) => { + const instance = instances[0]; + const imageSet = new ImageSet(instances); + + imageSet.setAttributes({ + displaySetInstanceUID: imageSet.uid, + SeriesDate: instance.SeriesDate, + SeriesTime: instance.SeriesTime, + SeriesInstanceUID: instance.SeriesInstanceUID, + StudyInstanceUID: instance.StudyInstanceUID, + SeriesNumber: instance.SeriesNumber, + FrameRate: instance.FrameTime, + SeriesDescription: instance.SeriesDescription, + Modality: instance.Modality, + isMultiFrame: isMultiFrame(instance), + numImageFrames: instances.length, + SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, + }); + + return imageSet; +}; + +getSopClassHandlerModule = () => { + return [ + { + name: 'stack, + sopClassUids, + getDisplaySetsFromSeries: makeDisplaySet, + }, + ]; +}; + +``` + +### More examples : +You can find another example for this mapping between raw metadata and displaySet for +`DICOM-SR` extension. + +## `@ohif/viewer` usage + +We use the `sopClassHandlerModule`s in `DisplaySetService` where we +transform instances from the raw metadata format to a OHIF displaySet format. +You can read more about DisplaySetService here. + + +[sop-class-link]: http://dicom.nema.org/dicom/2013/output/chtml/part04/sect_B.5.html +[dicom-html-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-html/src/OHIFDicomHtmlSopClassHandler.js#L4-L12 +[dicom-pdf-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-pdf/src/OHIFDicomPDFSopClassHandler.js#L4-L6 +[dicom-micro-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js#L5-L7 +[dicom-seg-sop]: https://github.com/OHIF/Viewers/blob/master/extensions/dicom-segmentation/src/OHIFDicomSegSopClassHandler.js#L5-L7 + diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md new file mode 100644 index 00000000000..f340e0c4e2c --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/toolbar.md @@ -0,0 +1,240 @@ +--- +sidebar_position: 1 +sidebar_label: Toolbar +--- + +# Module: Toolbar + +An extension can register a Toolbar Module by defining a `getToolbarModule` +method. `OHIF-v3`'s `default` extension (`"@ohif/extension-default"`) provides 5 main +toolbar button types: + +![toolbarModule](../../../assets/img/toolbar-module.png) + +## Example Toolbar Module + +The Toolbar Module should return an array of `objects`. There are currently a +few different variations of definitions, each one is detailed further down. + +```js +export default function getToolbarModule({ commandsManager, servicesManager }) { + return [ + { + name: 'ohif.divider', + defaultComponent: ToolbarDivider, + clickHandler: () => {}, + }, + { + name: 'ohif.action', + defaultComponent: ToolbarButton, + clickHandler: () => {}, + }, + { + name: 'ohif.radioGroup', + defaultComponent: ToolbarButton, + clickHandler: () => {}, + }, + { + name: 'ohif.splitButton', + defaultComponent: ToolbarSplitButton, + clickHandler: () => {}, + }, + { + name: 'ohif.layoutSelector', + defaultComponent: ToolbarLayoutSelector, + clickHandler: (evt, clickedBtn, btnSectionName) => {}, + }, + ]; +} +``` + +## Toolbar buttons consumed in modes + +Below we can see a simplified version of the `longitudinal` mode that shows how +a mode can add buttons to the toolbar by calling +`ToolBarService.addButtons(toolbarButtons)`. `toolbarButtons` is an array of +`toolDefinitions` which we will learn next. + +```js +function modeFactory({ modeConfiguration }) { + return { + id: 'viewer', + displayName: 'Basic Viewer', + + onModeEnter: ({ servicesManager, extensionManager }) => { + const { ToolBarService } = servicesManager.services; + + ToolBarService.init(extensionManager); + ToolBarService.addButtons(toolbarButtons); + }, + routes: [ + { + path: 'longitudinal', + layoutTemplate: ({ location, servicesManager }) => { + return { + /* */ + }; + }, + }, + ], + }; +} +``` + +## Button Definitions + +The simplest toolbarButtons definition has the following properties: + +![toolbarModule-zoom](../../../assets/img/toolbarModule-zoom.png) + +```js +{ + id: 'Zoom', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-zoom', + label: 'Zoom', + commandOptions: { toolName: 'Zoom' }, + }, +}, +``` + +| property | description | values | +| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | +| `id` | Unique string identifier for the definition | \* | +| `label` | User/display friendly to show in UI | \* | +| `icon` | A string name for an icon supported by the consuming application. | \* | +| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | +| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | +| `commandOptions` | (optional) Options to pass the target `commandName` | \* | + +There are three main types of toolbar buttons: + +- `tool`: buttons that enable a tool by running the `setToolActive` command with + the `commandOptions` +- `toggle`: buttons that acts as a toggle: e.g., linking viewports +- `action`: buttons that executes an action: e.g., capture button to save + screenshot + +## Nested Buttons + +You can use the `ohif.splitButton` type to build a button with extra tools in +the dropdown. + +- First you need to give your `primary` tool definition to the split button +- the `secondary` properties can be a simple arrow down (`chevron-down` icon) +- For adding the extra tools add them to the `items` list. + +You can see below how `longitudinal` mode is using the available toolbarModule +to create `MeasurementTools` nested button + +![toolbarModule-nested-buttons](../../../assets/img/toolbarModule-nested-buttons.png) + +```js title="modes/longitudinal/src/toolbarButtons.js" +{ + id: 'MeasurementTools', + type: 'ohif.splitButton', + props: { + groupId: 'MeasurementTools', + isRadio: true, + primary: { + id: 'Length', + icon: 'tool-length', + label: 'Length', + type: 'tool', + commandOptions: { + toolName: 'Length', + } + }, + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Measure Tools', + }, + items: [ + // Length tool + { + id: 'Length', + icon: 'tool-length', + label: 'Length', + type: 'tool', + commandOptions: { + toolName: 'Length', + } + }, + // Bidirectional tool + { + id: 'Bidirectional', + icon: 'tool-bidirectional', + label: 'Length', + type: 'tool', + commandOptions: { + toolName: 'Bidirectional', + } + }, + // Ellipse tool + { + id: 'EllipticalRoi', + icon: 'tool-elipse', + label: 'Ellipse', + type: 'tool', + commandOptions: { + toolName: 'EllipticalRoi', + } + }, + ], + }, +} +``` + +
+ +
+ +## Layout Template + +Layout selector button and logic is also provided by the OHIF-v3 `default` +extension. To use it, you can just add the following definition to the list of +`toolDefinitions` + +![toolbarModule-layout](../../../assets/img/toolbarModule-layout.png) + +```js +{ + id: 'Layout', + type: 'ohif.layoutSelector', +} +``` + +
+ +
+ +## Custom Button + +You can also create your own extension, and add your new custom tool appearance +(e.g., split horizontally instead of vertically for split tool). Simply add +`getToolbarModule` to your extension, and pass your tool react component to its +`defaultComponent` property in the returned object. You can use `@ohif/ui` +components such as `IconButton, Icon, Tooltip, ToolbarButton` to build your own +component. + +```js +import myToolComponent from './myToolComponent'; + +export default function getToolbarModule({ commandsManager, servicesManager }) { + return [ + { + name: 'new-tool-type', + defaultComponent: myToolComponent, + clickHandler: () => {}, + }, + ]; +} +``` + +## Custom tool + +**I want to create a new tool** diff --git a/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md new file mode 100644 index 00000000000..b49b33f6661 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/extensions/modules/viewport.md @@ -0,0 +1,96 @@ +--- +sidebar_position: 5 +sidebar_label: Viewport +--- + +# Module: Viewport + +## Overview + +Viewports consume a displaySet and display/allow the user to interact with data. +An extension can register a Viewport Module by defining a `getViewportModule` +method that returns a React component. Currently, we use viewport components to +add support for: + +- 2D Medical Image Viewing (cornerstone ext.) +- Structured Reports as SR (DICOM SR ext.) +- Structured Reports as HTML (DICOM html ext.) +- Encapsulated PDFs as PDFs (DICOM pdf ext.) +- Whole Slide Microscopy Viewing (whole slide ext.) +- etc. + +The general pattern is that a mode can define which `Viewport` to use for which +specific `SOPClassHandlerUID`, so if you want to fork just a single Viewport +component for a specialized mode, this is possible. + +```jsx +// displaySet, viewportIndex, dataSource +const getViewportModule = () => { + const wrappedViewport = props => { + return ( + { + commandsManager.runCommand('commandName', data); + }} + /> + ); + }; + + return [{ name: 'example', component: wrappedViewport }]; +}; +``` + +## Example Viewport Component + +A simplified version of the tracked CornerstoneViewport is shown below, which +creates a cornerstone viewport and action bar on top of it. + +```jsx +function TrackedCornerstoneViewport({ + children, + dataSource, + displaySet, + viewportIndex, + servicesManager, + extensionManager, + commandsManager, +}) { + const renderViewport = () => { + const { component: Component } = extensionManager.getModuleEntry( + '@ohif/extension-cornerstone.viewportModule.cornerstone' + ); + return ( + + ); + }; + + return ( + <> + +
+ {renderViewport()} +
+ + ); +} +``` + +![viewportModule](../../../assets/img/viewportModule.png) + +### `@ohif/viewer` + +Viewport components are managed by the `ViewportGrid` Component. Which Viewport +component is used depends on: + +- Hanging Protocols +- The Layout Configuration +- Registered SopClassHandlers + +![viewportModule-layout](../../../assets/img/viewportModule-layout.png) + +
An example of three cornerstone Viewports
diff --git a/platform/docs/versioned_docs/version-3.0/platform/internationalization.md b/platform/docs/versioned_docs/version-3.0/platform/internationalization.md new file mode 100644 index 00000000000..1bb32de5ba5 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/internationalization.md @@ -0,0 +1,396 @@ +--- +sidebar_position: 4 +sidebar_label: Internationalization +--- + +# Viewer: Internationalization + +OHIF supports internationalization using [i18next](https://www.i18next.com/) +through the npm package [@ohif/i18n](https://www.npmjs.com/package/@ohif/i18n), +where is the main instance of i18n containing several languages and tools. + +
+

Our translation management is powered by + Locize + through their generous support of open source.

+ + Locize Translation Management Logo + +
+ +## How to change language for the viewer? + +You can take a look into user manuals to see how to change the viewer's +language. In summary, you can change the language: + +- In the preference modals +- Using the language query in the URL: `lng=Test-LNG` + +## Installing + +```bash +yarn add @ohif/i18n + +# OR + +npm install --save @ohif/i18n +``` + +## How it works + +After installing `@ohif/i18n` npm package, the translation function +[t](https://www.i18next.com/overview/api#t) can be used [with](#with-react) or +[without](#without-react) React. + +A translation will occur every time a text match happens in a +[t](https://www.i18next.com/overview/api#t) function. + +The [t](https://www.i18next.com/overview/api#t) function is responsible for +getting translations using all the power of i18next. + +E.g. + +Before: + +```html +
my translated text
+``` + +After: + +```html +
{t('my translated text')}
+``` + +If the translation.json file contains a key that matches the HTML content e.g. +`my translated text`, it will be replaced automatically by the +[t](https://www.i18next.com/overview/api#t) function. + +--- + +### With React + +This section will introduce you to [react-i18next](https://react.i18next.com/) +basics and show how to implement the [t](https://www.i18next.com/overview/api#t) +function easily. + +#### Using Hooks + +You can use `useTranslation` hooks that is provided by `react-i18next` + +You can read more about this +[here](https://react.i18next.com/latest/usetranslation-hook). + +```js +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +function MyComponent() { + const { t } = useTranslation(); + + return

{t('my translated text')}

; +} +``` + +### Using outside of OHIF viewer + +OHIF Viewer already sets a main +[I18nextProvider](https://react.i18next.com/latest/i18nextprovider) connected to +the shared i18n instance from `@ohif/i18n`, all extensions inside OHIF Viewer +will share this same provider at the end, you don't need to set new providers at +all. + +But, if you need to use it completely outside of OHIF viewer, you can set the +I18nextProvider this way: + +```jsx +import i18n from '@ohif/i18n'; +import { I18nextProvider } from 'react-i18next'; +import App from './App'; + + + +; +``` + +After setting `I18nextProvider` in your React App, all translations from +`@ohif/i18n` should be available following the basic [With React](#with-react) +usage. + +--- + +### Without React + +When needed, you can also use available translations _without React_. + +E.g. + +```js +import { T } from '@ohif/i18n'; +console.log(T('my translated text')); +console.log(T('$t(Common:Play) my translated text')); +``` + +--- + +## Main Concepts While Translating + +## Namespaces + +Namespaces are being used to organize translations in smaller portions, combined +semantically or by use. Each `.json` file inside `@ohif/i18n` npm package +becomes a new namespace automatically. + +- Buttons: All buttons translations +- CineDialog: Translations for the tool tips inside the Cine Player Dialog +- Common: all common jargons that can be reused like `t('$t(common:image)')` +- Header: translations related to OHIF's Header Top Bar +- MeasurementTable - Translations for the `@ohif/ui` Measurement Table +- UserPreferencesModal - Translations for the `@ohif/ui` Preferences Modal +- Modals - Translations available for other modals +- PatientInfo - Translations for patients info hover +- SidePanel - Translations for side panels +- ToolTip - Translations for tool tips + +### How to use another NameSpace inside the current NameSpace? + +i18next provides a parsing feature able to get translations strings from any +NameSpace, like this following example getting data from `Common` NameSpace: + +``` +$t('Common:Reset') +``` + +## Extending Languages in @ohif/i18n + +Sometimes, even using the same language, some nouns or jargons can change +according to the country, states or even from Hospital to Hospital. + +In this cases, you don't need to set an entire language again, you can extend +languages creating a new folder inside a pre existent language folder and +@ohif/i18n will do the hard work. + +This new folder must to be called with a double character name, like the `UK` in +the following file tree: + +```bash + |-- src + |-- locales + index.js + |-- en + |-- Buttons.json + index.js + | UK + |-- Buttons.js + indes.js + | US + |-- Buttons.js + index.js + ... +``` + +All properties inside a Namespace will be merged in the new sub language, e.g +`en-US` and `en-UK` will merge the props with `en`, using i18next's fallback +languages tool. + +You will need to export all Json files in your `index.js` file, mounting an +object like this: + +```js + { + en: { + NameSpace: { + keyWord1: 'keyWord1Translation', + keyWord2: 'keyWord2Translation', + keyWord3: 'keyWord3Translation', + } + }, + 'en-UK': { + NameSpace: { + keyWord1: 'keyWord1DifferentTranslation', + } + } + } +``` + +Please check the `index.js` files inside locales folder for an example of this +exporting structure. + +### Extending languages dynamically + +You have access to the i18next instance, so you can use the +[addResourceBundle](https://www.i18next.com/how-to/add-or-load-translations#add-after-init) +method to add and change language resources as needed. + +E.g. + +```js +import { i18n } from '@ohif/i18n'; +i18next.addResourceBundle('pt-BR', 'Buttons', { + Angle: 'Ângulo', +}); +``` + +--- + +### How to set a whole new language + +To set a brand new language you can do it in two different ways: + +- Opening a pull request for `@ohif/i18n` and sharing the translation with the + community. 😍 Please see [Contributing](#contributing-with-new-languages) + section for further information. + +- Setting it only in your project or extension: + +You'll need a final object like the following, what is setting French as +language, and send it to `addLocales` method. + +```js +const newLanguage = + { + fr: { + Commons: { + "Reset": "Réinitialiser", + "Previous": "Précédent", + }, + Buttons: { + "Rectangle": "Rectangle", + "Circle": "Cercle", + } + } +``` + +To make it easier to translate, you can copy the .json files in the /locales +folder and theirs index.js exporters, keeping same keys and NameSpaces. +Importing the main index.js file, will provide you an Object as expected by the +method `addlocales`; + +E.g. of `addLocales` usage + +```js +import { addLocales } from '@ohif/i18n'; +import locales from './locales/index.js'; +addLocales(locales); +``` + +You can also set them manually, one by one, using this +[method](#extending-languages-dynamically). + +--- + +## Test Language + +We have created a test language that its translations can be seen in the locales +folder. You can copy paste the folder and its `.json` namespaces and add your +custom language translations. + +> If you apply the test-LNG you can see all the elements get appended with 'Test +> {}'. For instance `Study list` becomes `Test Study list`. + +## Language Detections + +@ohif/i18n uses +[i18next-browser-languageDetector](https://github.com/i18next/i18next-browser-languageDetector) +to manage detections, also exports a method called initI18n that accepts a new +detector config as parameter. + +### Changing the language + +OHIF Viewer accepts a query param called `lng` in the url to change the +language. + +E.g. + +``` +https://docs.ohif.org/demo/?lng=es-MX +``` + +### Language Persistence + +The user's language preference is kept automatically by the detector and stored +at a cookie called 'i18next', and in a localstorage key called 'i18nextLng'. +These names can be changed with a new +[Detector Config](https://github.com/i18next/i18next-browser-languageDetector). + +## Debugging translations + +There is an environment variable responsible for debugging the translations, +called `REACT_APP_I18N_DEBUG`. + +Run the project as following to get full debug information: + +```bash +REACT_APP_I18N_DEBUG=true yarn run dev +``` + +## Contributing with new languages + +We have integrated `i18next` into the OHIF Viewer and hooked it up with Locize +for translation management. Now we need your help to get the app translated into +as many languages as possible, and ensure that we haven't missed pieces of the +app that need translation. Locize has graciously offered to provide us with free +usage of their product. + +Once each crowd-sourcing project is completed, we can approve it and merge the +changes into the main project. At that point, the language will be immediately +available on https://viewer.ohif.org/ for testing, and can be used in any OHIF +project. We will support usage through both the Locize CDN and by copying the +language directly into the `@ohif/i18n` package, so that end users can serve the +content from their own domains. + +Here are a couple examples: + +Spanish: +https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=es + +Chinese: +https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=zh + +Portugese: +https://viewer.ohif.org/viewer/1.2.840.113619.2.5.1762583153.215519.978957063.78?lng=pt-BR + +Here are some links you can use to sign up to help translate. All you have to do +is sign up, translate the strings, and click Save. On our side, we have a +dashboard to see how many strings are translated and by whom. + +This is a pretty random set of languages, so please post below if you'd like a +new language link to be added: + +Languages: + +[French](https://www.locize.io/register?invitation=Nj8jRPaFKYwtIfNZ6Y5GVOJOpeiXNAdVuSiOg9ceaiveP6uF6y1wVXM9lgfKoYZX) + +[German](https://www.locize.io/register?invitation=gChNiVi66YINTPpbKESVAVYPapwg3DkpvMSSomLTvVqBJTXrdmPvxi0WZYHER11q) + +[Dutch](https://www.locize.io/register?invitation=2PGe7I184aN0cazM4GXMhzeLtGTf9Zen5uyOEFhHQ8vYkfKHkgR0mJ8dwbNlIeCG) + +[Turkish](https://www.locize.io/register?invitation=NOMIXsfneqPbFDqjce5wI7Z6p2swXSjc0rHOH4KLcM6qXSNA4LGyJaLxS7nqWAe3) + +[Chinese](https://www.locize.io/register?invitation=lrcUbt7DvV4aJmQeEA4SMAj5xNWr3rltOcaZW1cFc6eod0nvzSPFU4V383tDHGGn) + +[Japanese](https://www.locize.io/register?invitation=AaRq2S22o5FsxArwgVuw1gZcQjoe2ffyxarqlAXOpN7JnR2sf2mfamc5qV6LG1Mn) + +[Arabic](https://www.locize.io/register?invitation=BiqI6fOm1sC84N3YJLbImXmaOCk8Hc3TMGpXg7NH2R0b0OKuPCp9wlCHLoqMRpfQ) + +[Hindi](https://www.locize.io/register?invitation=ph7JmOGTV95DF3EFaI1kvK5Hx98dV9w2wj9h9UhUCWnkBNAwWEdWMcyjnF94zkWb) + +[Malay](https://www.locize.io/register?invitation=HsV9F5mKZyeUZYrC3XFRzNI2l0EsIh6hK0MUIKP8IYZA3GxuzfgkvWBLCFwCpDik) + +[Russian](https://www.locize.io/register?invitation=da4V9Q8DVO3M1FIlvfT50ZiS8NDNgvC0dE5hHUEAp47FXy6pLXmf1cp2lgLBfLmb) + +[Swedish](https://www.locize.io/register?invitation=uR4kzBZC1vhJe6jyMwYXgGPj84QDMulQRlt2s6rONU6ljUh5dgwuUyhJEtZ4REA3) + +[Italian](https://www.locize.io/register?invitation=viAS1NC5q342OxtuIv3JFX9DJ3KoR4SmGoElkBlRMphsDKt4hy9bW8JfBjHlfnd7) + +[Spanish](https://www.locize.io/register?invitation=ZikXW3KI4w4eo5Cf6L1aQMWaR69XAQ0a9Va3NGorH7mAPvEPXp8w8NLkPNLs5nG8) + +[Ukrainian](https://www.locize.io/register?invitation=TY0s6onqH3Asl05Bh1qB44SNSABL2pTYoturwxAmcNKRnzBZFK7bGfn7kVi23Vpg) + +[Vietnamese](https://www.locize.io/register?invitation=eqfHDm0vaqxGfQ5TGt6SeV0dx9b2dCp1RrMRdIRavqzOCOAfD3IElzUsyIT689cK) + +[Portugese-Brazil](https://www.locize.io/register?invitation=Qc5Dq449xbblQqLTpWeMfsyFiu3gACcgpj0EIucQjjs9Ph9pzPLpq3MnZupF9t6N) + +Don't see your language in the above list? Add a request +[here](https://github.com/OHIF/Viewers/issues/618) so that we can create the +language for your translation contribution. diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json new file mode 100644 index 00000000000..30940779278 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/managers/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Managers", + "position": 11 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md b/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md new file mode 100644 index 00000000000..fab666dacad --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/managers/commands.md @@ -0,0 +1,180 @@ +--- +sidebar_position: 4 +sidebar_label: Commands Manager +--- +# Commands Manager + +## Overview + + +The `CommandsManager` is a class defined in the `@ohif/core` project. The Commands Manager tracks named commands (or functions) that are scoped to +a context. When we attempt to run a command with a given name, we look for it +in our active contexts. If found, we run the command, passing in any application +or call specific data specified in the command's definition. + +> Note: A single instance of `CommandsManager` should be defined in the consuming application, and it is used when constructing the `ExtensionManager`. + +A `simplified skeleton` of the `CommandsManager` is shown below: + +```js +export class CommandsManager { + constructor({ getActiveContexts } = {}) { + this.contexts = {}; + this._getActiveContexts = getActiveContexts; + } + + getContext(contextName) { + const context = this.contexts[contextName]; + return context; + } + + /**...**/ + + createContext(contextName) { + /** ... **/ + this.contexts[contextName] = {}; + } + + + registerCommand(contextName, commandName, definition) { + /**...**/ + const context = this.getContext(contextName); + /**...**/ + context[commandName] = definition; + } + + runCommand(commandName, options = {}, contextName) { + const definition = this.getCommand(commandName, contextName); + /**...**/ + const { commandFn } = definition; + const commandParams = Object.assign( + {}, + definition.options, // "Command configuration" + options // "Time of call" info + ); + /**...**/ + return commandFn(commandParams); + } + /**...**/ +} +``` + + + + +### Instantiating + +When we instantiate the `CommandsManager`, we are passing two methods: + +- `getAppState` - Should return the application's state when called (Not implemented in `v3`) +- `getActiveContexts` - Should return the application's active contexts when + called + +These methods are used internally to help determine which commands are currently +valid, and how to provide them with any state they may need at the time they are +called. + +```js title="platform/viewer/src/appInit.js" +const commandsManagerConfig = { + getAppState: () => {}, + /** Used by commands to determine active context */ + getActiveContexts: () => [ + 'VIEWER', + 'DEFAULT', + 'ACTIVE_VIEWPORT::CORNERSTONE', + ], +}; + +const commandsManager = new CommandsManager(commandsManagerConfig); +``` + + +## Commands/Context Registration +The `ExtensionManager` handles registering commands and creating contexts, so you don't need to register all your commands manually. Simply, create a `commandsModule` in your extension, and it will get automatically registered in the `context` provided. + +A *simplified version* of this registration is shown below to give an idea about the process. + + +```js +export default class ExtensionManager { + constructor({ commandsManager }) { + this._commandsManager = commandsManager + } + /** ... **/ + registerExtension = (extension, configuration = {}, dataSources = []) => { + let extensionId = extension.id + /** ... **/ + + // Register Modules provided by the extension + moduleTypeNames.forEach((moduleType) => { + const extensionModule = this._getExtensionModule( + moduleType, + extension, + extensionId, + configuration + ) + + if (moduleType === 'commandsModule') { + this._initCommandsModule(extensionModule) + } + /** registering other modules **/ + }) + } + + _initCommandsModule = (extensionModule) => { + let { definitions, defaultContext } = extensionModule + defaultContext = defaultContext || 'VIEWER' + + if (!this._commandsManager.getContext(defaultContext)) { + this._commandsManager.createContext(defaultContext) + } + + Object.keys(definitions).forEach((commandName) => { + const commandDefinition = definitions[commandName] + const commandHasContextThatDoesNotExist = + commandDefinition.context && + !this._commandsManager.getContext(commandDefinition.context) + + if (commandHasContextThatDoesNotExist) { + this._commandsManager.createContext(commandDefinition.context) + } + + this._commandsManager.registerCommand( + commandDefinition.context || defaultContext, + commandName, + commandDefinition + ) + }) + } +} + +``` + + +If you find yourself in a situation where you want to register a command/context manually, ask +yourself "why can't I register these commands via an extension?", but if you insist, you can use the `CommandsManager` API to do so: + +```js +// Command Registration +commandsManager.registerCommand('context', 'name', commandDefinition); + +// Context Creation +commandsManager.createContext('string'); +``` + +## `CommandsManager` Public API + +If you would like to run a command in the consuming app or an extension, you can +use `runCommand(commandName, options = {}, contextName)`. + + +```js +// Run a command, it will run all the `speak` commands in all contexts +commandsManager.runCommand('speak', { command: 'hello' }); + +// Run command, from Default context +commandsManager.runCommand('speak', { command: 'hello' }, ['DEFAULT']); + +// Returns all commands for a given context +commandsManager.getContext('string'); +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md b/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md new file mode 100644 index 00000000000..9fb5196f840 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/managers/extension.md @@ -0,0 +1,55 @@ +--- +sidebar_position: 2 +sidebar_label: Extension Manager +--- + +# Extension Manager + +## Overview + +The `ExtensionManager` is a class made available to us via the `@ohif/core` +project (platform/core). Our application instantiates a single instance of it, +and provides a `ServicesManager` and `CommandsManager` along with the +application's configuration through the appConfig key (optional). + +```js +const commandsManager = new CommandsManager(); +const servicesManager = new ServicesManager(); +const extensionManager = new ExtensionManager({ + commandsManager, + servicesManager, + appConfig, +}); +``` + +The `ExtensionManager` only has a few public members: + +- `setActiveDataSource` - Sets the active data source for the application +- `getDataSources` - Returns the registered data sources +- `getActiveDataSource` - Returns the currently active data source +- `getModuleEntry` - Returns the module entry by the give id. + +## Accessing Modules + +We use `getModuleEntry` in our `ViewerLayout` logic to find the panels based on +the provided IDs in the mode's configuration. + +For instance: +`extensionManager.getModuleEntry("@ohif/extension-measurement-tracking.panelModule.seriesList")` +accesses the `seriesList` panel from `panelModule` of the +`@ohif/extension-measurement-tracking` extension. + +```js +const getPanelData = id => { + const entry = extensionManager.getModuleEntry(id); + const content = entry.component; + + return { + iconName: entry.iconName, + iconLabel: entry.iconLabel, + label: entry.label, + name: entry.name, + content, + }; +}; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md b/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md new file mode 100644 index 00000000000..ebdd2bd1334 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/managers/hotkeys.md @@ -0,0 +1,79 @@ +--- +sidebar_position: 5 +sidebar_label: Hotkeys Manager +--- +# Hotkeys Managers + +## Overview +`HotkeysManager` handles all the logics for adding, setting and enabling/disabling +the hotkeys. + + + +## Instantiation +`HotkeysManager` is instantiated in the `appInit` similar to the other managers. + +```js +const commandsManager = new CommandsManager(commandsManagerConfig); +const servicesManager = new ServicesManager(commandsManager); +const hotkeysManager = new HotkeysManager(commandsManager, servicesManager); +const extensionManager = new ExtensionManager({ + commandsManager, + servicesManager, + hotkeysManager, + appConfig, +}); +``` + + + + +## Hotkeys Manager API + +- `setHotkeys`: The most important method in the `HotkeysManager` which binds the keys with commands. +- `setDefaultHotKeys`: set the defaultHotkeys **property**. Note that, this method **does not** bind the provided hotkeys; however, when `restoreDefaultBindings` +is called, the provided defaultHotkeys will get bound. +- `destroy`: reset the HotkeysManager, and remove the set hotkeys and empty out the `defaultHotkeys` + + + +## Structure of a Hotkey Definition +A hotkey definition should have the following properties: + +- `commandName`: name of the registered command +- `commandOptions`: extra arguments to the commands +- `keys`: an array defining the key to get bound to the command +- `label`: label to be shown in the hotkeys preference panel +- `isEditable`: whether the key can be edited by the user in the hotkey panel + + +### Default hotkeysBindings +The default key bindings can be find in `hotkeyBindings.js` + +```js +// platform/core/src/defaults/hotkeyBindings.js + +export default [ + /**..**/ + { + commandName: 'setToolActive', + commandOptions: { toolName: 'Zoom' }, + label: 'Zoom', + keys: ['z'], + isEditable: true, + }, + + { + commandName: 'flipViewportHorizontal', + label: 'Flip Vertically', + keys: ['v'], + isEditable: true, + }, + /**..**/ +] +``` + + +## Behind the Scene +When you `setHotkeys`, the `commandName` gets registered with the `commandsManager` and +get run after the key is pressed. diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/index.md b/platform/docs/versioned_docs/version-3.0/platform/managers/index.md new file mode 100644 index 00000000000..ee6d6b2fc8c --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/managers/index.md @@ -0,0 +1,76 @@ +--- +sidebar_position: 1 +sidebar_label: Introduction +--- + +# Managers + +## Overview + +`OHIF` uses `Managers` to accomplish various purposes such as registering new +services, dependency injection, and aggregating and exposing `extension` +features. + +`OHIF-v3` provides the following managers which we will discuss in depth. + + + + + + + + + + + + + + + + + + + + + + + + + + +
ManagerDescription
+ + Extension Manager + + + Aggregating and exposing modules and features through out the app +
+ + Services Manager + + + Single point of registration for all internal and external services +
+ + Commands Manager + + + Register commands with specific context and run commands in the app +
+ + Hotkeys Manager + + + For keyboard keys assignment to commands +
+ + + + + +[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services +[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js +[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern + diff --git a/platform/docs/versioned_docs/version-3.0/platform/managers/service.md b/platform/docs/versioned_docs/version-3.0/platform/managers/service.md new file mode 100644 index 00000000000..2cacda412e8 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/managers/service.md @@ -0,0 +1,162 @@ +--- +sidebar_position: 3 +sidebar_label: Service Manager +--- + +# Services Manager + +## Overview + +Services manager is the single point of service registration. Each service needs +to implement a `create` method which gets called inside `ServicesManager` to +instantiate the service. In the app, you can get access to a registered service +via the `services` property of the `ServicesManager`. + +## Skeleton + +_Simplified_ skeleton of `ServicesManager` is shown below. There are two public +methods: + +- `registerService`: registering a new service with/without a configuration +- `registerServices`: registering batch of services + +```js +export default class ServicesManager { + constructor(commandsManager) { + this._commandsManager = commandsManager; + this.services = {}; + this.registeredServiceNames = []; + } + + registerService(service, configuration = {}) { + /** validation checks **/ + this.services[service.name] = service.create({ + configuration, + commandsManager: this._commandsManager, + }); + + /* Track service registration */ + this.registeredServiceNames.push(service.name); + } + + registerServices(services) { + /** ... **/ + } +} +``` + +## Default Registered Services + +By default, `OHIF-v3` registers the following services in the `appInit`. + +```js title="platform/viewer/src/appInit.js" +servicesManager.registerServices([ + UINotificationService, + UIModalService, + UIDialogService, + UIViewportDialogService, + MeasurementService, + DisplaySetService, + ToolBarService, + ViewportGridService, + HangingProtocolService, + CineService, +]); +``` + +## Service Architecture + +If you take a look at the folder of each service implementation above, you will +find out that services need to be exported as an object with `name` and `create` +method. + +For instance, `ToolBarService` is exported as: + +```js title="platform/core/src/services/ToolBarService/index.js" +import ToolBarService from './ToolBarService'; + +export default { + name: 'ToolBarService', + create: ({ configuration = {}, commandsManager }) => { + return new ToolBarService(commandsManager); + }, +}; +``` + +and the implementation of `ToolBarService` lies in the same folder at +`./ToolbarSerivce.js`. + +> Note, the create method is critical for any custom service that you write and +> want to add to the list of services + +## Accessing Services + +Throughout the app you can use `services` property of the service manager to +access the desired service. + +For instance in the `PanelMeasurementTableTracking` which is the right panel in +the `longitudinal` mode, we have the _simplified code below_ for downloading the +drawn measurements. + +```js +function PanelMeasurementTableTracking({ servicesManager }) { + const { MeasurementService } = servicesManager.services; + /** ... **/ + + async function exportReport() { + const measurements = MeasurementService.getMeasurements(); + /** ... **/ + downloadCSVReport(measurements, MeasurementService); + } + + /** ... **/ + return <> /** ... **/ ; +} +``` + +## Registering Custom Services + +You might need to write you own custom service in an extension. +`preRegistration` hook inside your extension is the place for registering your +custom service. + +```js title="extensions/customExtension/src/index.js" +import WrappedBackEndService from './services/backEndService'; + +export default { + // ID of the extension + id: 'myExtension', + preRegistration({ servicesManager }) { + servicesManager.registerService(WrappedBackEndService(servicesManager)); + }, +}; +``` + +and the logic for your service shall be + +```js title="extensions/customExtension/src/services/backEndService/index.js" +import backEndService from './backEndService'; + +export default function WrappedBackEndService(serviceManager) { + return { + name: 'myService', + create: ({ configuration = {} }) => { + return new backEndService(serviceManager); + }, + }; +} +``` + +with implementation of + +```js +export default class backEndService { + constructor(serviceManager) { + this.serviceManager = serviceManager; + } + + putAnnotations() { + return post(/*...*/); + } +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json new file mode 100644 index 00000000000..80702b9e54e --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/modes/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Modes", + "position": 10 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/index.md b/platform/docs/versioned_docs/version-3.0/platform/modes/index.md new file mode 100644 index 00000000000..95421bfae96 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/modes/index.md @@ -0,0 +1,383 @@ +--- +sidebar_position: 1 +sidebar_label: Introduction +--- + +# Modes + +## Overview + +A mode can be thought of as a viewer app configured to perform a specific task, +such as tracking measurements over time, 3D segmentation, a guided radiological +workflow, etc. Addition of modes enables _application_ with many _applications_ +as each mode become a mini _app configuration_ behind the scene. + +Upon initialization the viewer will consume extensions and modes and build up +the route desired, these can then be accessed via the study list, or directly +via url parameters. + + + +OHIF-v3 architecture can be seen in the following: + +![mode-archs](../../assets/img/mode-archs.png) + +> Note: Templates are now a part of “extensions” Routes are configured by modes +> and/or app + +As mentioned, modes are tied to a specific route in the viewer, and multiple +modes/routes can be present within a single application. This allows for +tremendously more flexibility than before you can now: + +- Simultaneously host multiple viewers with for different use cases from within + the same app deploy. +- Make radiological viewers for specific purposes/workflows, e.g.: + - Tracking the size of lesions over time. + - PET/CT fusion workflows. + - Guided review workflows optimized for a specific clinical trial. +- Still host one single feature-rich viewer if you desire. + +## Anatomy + +A mode configuration has a `route` name which is dynamically transformed into a +viewer route on initialization of the application. Modes that are available to a +study will appear in the study list. + +![user-study-summary](../../assets/img/user-study-summary.png) + +The mode configuration specifies which `extensions` the mode requires, which +`LayoutTemplate` to use, and what props to pass to the template. For the default +template this defines which `side panels` will be available, as well as what +`viewports` and which `displaySets` they may hang. + +Mode's config is composed of three elements: +- `id`: the mode `id` +- `modeFactory`: the function that returns the mode specific configuration +- `extensionDependencies`: the list of extensions that the mode requires + + +that return a config object with certain +properties, the high-level view of this config object is: + +```js title="modes/example/src/index.js" +function modeFactory() { + return { + id: '', + version: '', + displayName: '', + onModeEnter: () => {}, + onModeExit: () => {}, + validationTags: {}, + isValidMode: () => {}, + routes: [ + { + path: '', + init: () => {}, + layoutTemplate: () => {}, + }, + ], + extensions: extensionDependencies, + hangingProtocols: [], + sopClassHandlers: [], + hotkeys: [], + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +}; + +export default mode; +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyDescription
+ id + unique mode id used to refer to the mode
+ displayName + actual name of the mode being displayed for each study in the study summary panel
+ + onModeEnter + + hook is called when the mode is entered by the specified route
+ + onModeExit + + hook is called when the mode exited
+ + validationTags + + validationTags
+ + isValidMode + + Checks if the mode is valid for a study
+ + routes + + route config which defines the route address, and the layout for it
+ + extensionDependencies + + extensions needed by the mode
+ + hanging protocol + + list of hanging protocols that the mode should have access to
+ + sopClassHandlers + + list of SOPClass modules needed by the mode
+ + hotkeys + + hotkeys
+ +### Consuming Extensions + +As mentioned in the [Extensions](../extensions/index.md) section, in `OHIF-v3` +developers write their extensions to create re-usable functionalities that later +can be used by `modes`. Now, it is time to describe how the registered +extensions will get utilized for a workflow mode via its `id`. + +Each `mode` has a list of its `extensions dependencies` which are the +the `extension` name and version number. In addition, to use a module element you can use the +`${extensionId}.${moduleType}.${element.name}` schema. For instance, if a mode +requires the left panel with name of `AIPanel` that is added by the +`myAIExtension` via the following `getPanelModule` code, it should address it as +`myAIExtension.panelModule.AIPanel` inside the mode configuration file. In the +background `OHIF` will handle grabbing the correct panel via `ExtensionManager`. + +```js title="extensions/myAIExtension/getPanelModule.js" +import PanelAI from './PanelAI.js'; + +function getPanelModule({ + commandsManager, + extensionManager, + servicesManager, +}) { + const wrappedAIPanel = () => { + return ( + + ); + }; + + return [ + { + name: 'AIPanel', + iconName: 'list-bullets', + iconLabel: '', + label: 'AI Panel', + isDisabled: studies => {}, // optional + component: wrappedAIPanel, + }, + ]; +} +``` + +Now, let's look at a simplified code of the `basic viewer` mode which consumes various functionalities +from different extensions. + +```js + +const extensionDependencies = { + '@ohif/extension-default': '^3.0.0', + '@ohif/extension-cornerstone': '^3.0.0', + '@ohif/extension-measurement-tracking': '^3.0.0', +}; + +const id = 'viewer'; +const version = '3.0.0'; + +function modeFactory({ modeConfiguration }) { + return { + id, + // ... + routes: [ + { + // ... + layoutTemplate: ({ location, servicesManager }) => { + return { + id: ohif.layout, + props: { + leftPanels: ['@ohif/extension-measurement-tracking.panelModule.seriesList'], + rightPanels: ['@ohif/extension-measurement-tracking.panelModule.trackedMeasurements'], + viewports: [ + { + namespace: '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', + displaySetsToDisplay: ['@ohif/extension-default.sopClassHandlerModule.stack'], + }, + ], + }, + }; + }, + }, + ], + extensions: extensionDependencies, + hangingProtocols: ['@ohif/extension-default.hangingProtocolModule.petCT'], + sopClassHandlers: ['@ohif/extension-default.sopClassHandlerModule.stack'], + // ... + }; +} + +const mode = { + id, + modeFactory, + extensionDependencies, +} + +export default mode +``` + +### Routes + +routes config is an array of route settings, and the overall look and behavior +of the viewer at the designated route is defined by the `layoutTemplate` and +`init` functions for the route. We will learn more about each of the above +properties inside the [route documentation](./routes.md) + + +### HangingProtocols + +Currently, you can pass your defined hanging protocols inside the +`hangingProtocols` property of the mode's config. This will get registered +inside `HangingProtocolService`. + +### SopClassHandlers + +Mode's configuration also accepts the `sopClassHandler` modules that have been +added by the extensions. This information will get used to initialize `DisplaySetService` with the provided SOPClass modules which +handles creation of the displaySets. + + +### Hotkeys + +`hotkeys` is another property in the configuration of a mode that can be defined +to add the specific hotkeys to the viewer at all routes. + +```js +// default hotkeys +import { utils } from '@ohif/ui'; + +const { hotkeys } = utils; + +const myHotkeys = [ + { + commandName: 'setToolActive', + commandOptions: { toolName: 'Zoom' }, + label: 'Zoom', + keys: ['z'], + isEditable: true, + }, + { + commandName: 'scaleUpViewport', + label: 'Zoom In', + keys: ['+'], + isEditable: true, + }, +] + +function modeFactory() { + return { + id: '', + id: '', + displayName: '', + /* + ... + */ + hotkeys: [..hotkeys.defaults.hotkeyBindings, ...myHotkeys], + } +} + +// exports +``` + +## Registration + +Similar to extension registration, `viewer` will look inside the `pluginConfig.json` to +find the `modes` to register. + + +```js title=platform/viewer/pluginConfig.json +// Simplified version of the `pluginConfig.json` file +{ + "extensions": [ + { + "packageName": "@ohif/extension-cornerstone", + "version": "3.0.0" + }, + // ... + ], + "modes": [ + { + "packageName": "@ohif/mode-longitudinal", + "version": "0.0.1" + } + ] +} +``` + +:::note Important +You SHOULD NOT directly register modes in the `pluginConfig.json` file. +Use the provided `cli` to add/remove/install/uninstall modes. Read more [here](../../development/ohif-cli.md) +::: + +The final registration and import of the modes happen inside a non-tracked file `pluginImport.js` (this file is also for internal use only). diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md b/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md new file mode 100644 index 00000000000..08c456d29e1 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/modes/installation.md @@ -0,0 +1,12 @@ +--- +sidebar_position: 5 +sidebar_label: Installation +--- + +# Modes: Installation + +OHIF-v3 provides the ability to utilize external modes. + + +You can use ohif `cli` tool to install both local and publicly published +modes on NPM. You can read more [here](../../development/ohif-cli.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md b/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md new file mode 100644 index 00000000000..32ea77cb352 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/modes/lifecycle.md @@ -0,0 +1,86 @@ +--- +sidebar_position: 2 +sidebar_label: Lifecycle Hooks +--- + +# Modes: Lifecycle Hooks + +## Overview + +Currently, there are two hooks that are called for modes: + +- onModeEnter +- onModeExit + +## onModeEnter + +This hook gets run after the defined route has been entered by the mode. This +hook can be used to initialize the data, services and appearance of the viewer +upon the first render. + +For instance, in `longitudinal` mode we are using this hook to initialize the +`ToolBarService` and set the window level/width tool to be active and add +buttons to the toolbar. + +```js +function modeFactory() { + return { + id: '', + version: '', + displayName: '', + onModeEnter: ({ servicesManager, extensionManager }) => { + const { ToolBarService } = servicesManager.services; + + const interaction = { + groupId: 'primary', + itemId: 'WindowLevel', + interactionType: 'tool', + commandOptions: undefined, + }; + + ToolBarService.recordInteraction(interaction); + + ToolBarService.init(extensionManager); + ToolBarService.addButtons(toolbarButtons); + ToolBarService.createButtonSection('primary', [ + 'MeasurementTools', + 'Zoom', + 'WindowLevel', + 'Pan', + 'Capture', + 'Layout', + 'MoreTools', + ]); + }, + /* + ... + */ + }; +} +``` + +## onModeExit + +This hook is called when the viewer navigate away from the route in the url. +This is the place for cleaning up data, and services by unsubscribing to the +events. + +For instance, it can be used to reset the `ToolBarService` which reset the +toggled buttons. + +```js +function modeFactory() { + return { + id: '', + displayName: '', + onModeExit: ({ servicesManager, extensionManager }) => { + // Turn of the toggled states on exit + const { ToolBarService } = servicesManager.services; + ToolBarService.reset(); + }, + /* + ... + */ + }; +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md b/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md new file mode 100644 index 00000000000..2204bd77ae7 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/modes/routes.md @@ -0,0 +1,315 @@ +--- +sidebar_position: 3 +sidebar_label: Routes +--- + +# Mode: Routes + +## Overview + +Modes are tied to a specific route in the viewer, and multiple modes/routes can +be present within a single application. This makes `routes` config, THE most +important part of the mode configuration. + +## Route + +`@ohif/viewer` **compose** extensions to build applications on different routes +for the platform. + +Below, you can see a simplified version of the `longitudinal` mode and the +`routes` section which has defined one `route`. Each route has three different +configuration: + +- **route path**: defines the route path to access the built application for + that route +- **route init**: hook that runs when application enters the defined route path, + if not defined the default init function will run for the mode. +- **route layout**: defines the layout of the application for the specified + route (panels, viewports) + +```js +function modeFactory() { + return { + id: 'viewer', + version: '3.0.0', + displayName: '', + routes: [ + { + path: 'longitudinal', + /*init: ({ servicesManager, extensionManager }) => { + //defaultViewerRouteInit + },*/ + layoutTemplate: ({ location, servicesManager }) => { + return { + id: ohif.layout, + props: { + leftPanels: [ + '@ohif/extension-measurement-tracking.panelModule.seriesList', + ], + rightPanels: [ + '@ohif/extension-measurement-tracking.panelModule.trackedMeasurements', + ], + viewports: [ + { + namespace: + '@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked', + displaySetsToDisplay: [ + '@ohif/extension-default.sopClassHandlerModule.stack', + ], + }, + { + namespace: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr', + displaySetsToDisplay: [ + '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr', + ], + }, + ], + }, + }; + }, + }, + ], + /* + ... + */ + }; +} +``` + +### Route: path + +Upon initialization the viewer will consume extensions and modes and build up +the route desired, these can then be accessed via the study list, or directly +via url parameters. + +> Note: Currently, only one route is built for each mode, but we will enhance +> route creation to create separate routes based on the `path` config for each +> `route` object. + +There are two types of `routes` that are created by the mode. + +- Routes with dataSourceName `/${mode.id}/${dataSourceName}` +- Routes without dataSourceName `/${mode.id}` + +Therefore, navigating to +`http://localhost:3000/viewer/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` +will run the app with the layout and functionalities of the `viewer` mode using +the `defaultDataSourceName` which is defined in the +[App Config](../../configuration/index.md) + +You can use the same exact mode using a different registered data source (e.g., +`dicomjson`) by navigating to +`http://localhost:3000/viewer/dicomjson/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` + +### Route: init + +The mode also has an init hook, which initializes the mode. If you don't define +an `init` function the `default init` function will get run (logic is located +inside `Mode.jsx`). However, you can define you own init function following +certain steps which we will discuss next. + +#### Default init + +Default init function will: + +- `retriveSeriesMetaData` for the `studyInstanceUIDs` that are defined in the + URL. +- Subscribe to `instanceAdded` event, to make display sets after a series have + finished retrieving its instances' metadata. +- Subscribe to `seriesAdded` event, to run the `HangingProtocolService` on the + retrieves series from the study. + +A _simplified_ "pseudocode" for the `defaultRouteInit` is: + +```jsx +async function defaultRouteInit({ + servicesManager, + studyInstanceUIDs, + dataSource, +}) { + const { + DisplaySetService, + HangingProtocolService, + } = servicesManager.services; + + // subscribe to run the function after the event happens + DicomMetadataStore.subscribe( + 'instancesAdded', + ({ StudyInstanceUID, SeriesInstanceUID }) => { + const seriesMetadata = DicomMetadataStore.getSeries( + StudyInstanceUID, + SeriesInstanceUID + ); + DisplaySetService.makeDisplaySets(seriesMetadata.instances); + } + ); + + studyInstanceUIDs.forEach(StudyInstanceUID => { + dataSource.retrieve.series.metadata({ StudyInstanceUID }); + }); + + DicomMetadataStore.subscribe('seriesAdded', ({ StudyInstanceUID }) => { + const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); + HangingProtocolService.run(studyMetadata); + }); + + return unsubscriptions; +} +``` + +#### Writing a custom init + +You can add your custom init function to enhance the default initialization for: + +- Fetching annotations from a server for the current study +- Changing the initial image index of the series to be displayed at first +- Caching the next study in the work list +- Adding a custom sort for the series to be displayed on the study browser panel + +and lots of other modifications. + +You just need to make sure, the mode `dataSource.retrieve.series.metadata`, +`makeDisplaySets` and `run` the HangingProtocols at some point. There are +various `events` that you can subscribe to and add your custom logic. **point to +events** + +For instance for jumping to the slice where a measurement is located at the +initial render, you need to follow a pattern similar to the following: + +```jsx +init: async ({ + servicesManager, + extensionManager, + hotkeysManager, + dataSource, + studyInstanceUIDs, +}) => { + const { DisplaySetService } = servicesManager.services; + + /** + ... + **/ + + const onDisplaySetsAdded = ({ displaySetsAdded, options }) => { + const displaySet = displaySetsAdded[0]; + const { SeriesInstanceUID } = displaySet; + + const toolData = myServer.fetchMeasurements(SeriesInstanceUID); + + if (!toolData.length) { + return; + } + + toolData.forEach(tool => { + const instance = displaySet.images.find( + image => image.SOPInstanceUID === tool.SOPInstanceUID + ); + }); + + MeasurementService.addMeasurement(/**...**/); + }; + + // subscription to the DISPLAY_SETS_ADDED + const { unsubscribe } = DisplaySetService.subscribe( + DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, + onDisplaySetsAdded + ); + + /** + ... + **/ + + return unsubscriptions; +}; +``` + +### Route: layoutTemplate + +`layoutTemplate` is the last configuration for a certain route in a `mode`. +`layoutTemplate` is a function that returns an object that configures the +overall layout of the application. The returned object has two properties: + +- `id`: the id of the `layoutTemplate` being used (it should have been + registered via an extension) +- `props`: the required properties to be passed to the `layoutTemplate`. + +For instance `default extension` provides a layoutTemplate that builds the app +using left/right panels and viewports. Therefore, the `props` include +`leftPanels`, `rightPanels` and `viewports` sections. Note that the +`layoutTemplate` defines the properties it is expecting. So, if you write a +`layoutTemplate-2` that accepts a footer section, its logic should be written in +the extension, and any mode that is interested in using `layoutTemplate-2` +**should** provide the `id` for the footer component. + +**What module should the footer be registered?** + +```js +/* +... +*/ +layoutTemplate: ({ location, servicesManager }) => { + return { + id: '@ohif/extension-default.layoutTemplateModule.viewerLayout', + props: { + leftPanels: [ + 'myExtension.panelModule.leftPanel1', + 'myExtension.panelModule.leftPanel2', + ], + rightPanels: ['myExtension.panelModule.rightPanel'], + viewports: [ + { + namespace: 'myExtension.viewportModule.viewport1', + displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop1'], + }, + { + namespace: 'myExtension.viewportModule.viewport2', + displaySetsToDisplay: ['myExtension.sopClassHandlerModule.sop2'], + }, + ], + }, + }; +}; +/* +... +*/ +``` + +## FAQ + +> What is the difference between `onModeEnter` and `route.init` + +`onModeEnter` gets run first than `route.init`; however, each route can have +their own `init`, but they share the `onModeEnter`. + +> How can I change the `workList` appearance or add a new login page? + +This is where `OHIF-v3` shines! Since the default `layoutTemplate` is written +for the viewer part, you can simply add a new `layoutTemplate` and use the +component you have written for that route. `Mode` handle showing the correct +component for the specified route. + +```js +function modeFactory() { + return { + id: 'viewer', + displayName: '', + routes: [ + { + path: 'worklist', + init, + layoutTemplate: ({ location, servicesManager }) => { + return { + id: 'worklistLayout', + props: { + component: 'myNewWorkList', + }, + }; + }, + }, + ], + /* + ... + */ + }; +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md b/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md new file mode 100644 index 00000000000..e3f70af7668 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/modes/validity.md @@ -0,0 +1,37 @@ +--- +sidebar_position: 4 +sidebar_label: Validity +--- +# Mode: Validity + + +## Overview +There are two mechanism for checking the validity of a mode for a study. + +- `isValidMode`: which is called on a selected study in the workList. +- `validTags` + + + +## isValidMode +This hook can be used to define a function that return a `boolean` which decided the +validity of the mode based on `StudyInstanceUID` and `modalities` that are in the study. + +For instance, for pet-ct mode, both `PT` and 'CT' modalities should be available inside the study. + +```js +function modeFactory() { + return { + id: '', + displayName: '', + isValidMode: ({ modalities, StudyInstanceUID }) => { + const modalities_list = modalities.split('\\'); + const validMode = ['CT', 'PT'].every(modality => modalities_list.includes(modality)); + return validMode; + }, + /* + ... + */ + } +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md b/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md new file mode 100644 index 00000000000..bdc411e6cb1 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/pwa-vs-packaged.md @@ -0,0 +1,34 @@ +--- +sidebar_position: 3 +--- + +# PWA vs Packaged + +It's important to know that the OHIF Viewer project provides two different build +processes: + +```bash +# Static Asset output: For deploying PWAs +yarn run build +``` + +## Progressive Web Application (PWA) + +> [Progressive Web Apps][pwa] are a new breed of web applications that meet the +> [following requirements][pwa-checklist]. Notably, targeting a PWA allows us +> provide a reliable, fast, and engaging experience across different devices and +> network conditions. + +The OHIF Viewer is maintained as a [monorepo][monorepo]. We use WebPack to build +the many small static assets that comprise our application. Also generated is an +`index.html` that will serve as an entry point for loading configuration and the +application, as well as a `service-worker` that can intelligently cache files so +that subsequent requests are from the local file system instead of over the +network. + +You can read more about this particular strategy in our +[Build for Production Deployment Guide](./../deployment/build-for-production.md) + +## Commonjs Bundle (Packaged Script) + +We are not supporting `Commonjs` bundling inside `OHIF-v3`. diff --git a/platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md b/platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md new file mode 100644 index 00000000000..4ac68daa60e --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/scope-of-project.md @@ -0,0 +1,69 @@ +--- +sidebar_position: 1 +--- +# Scope of Project + +The OHIF Viewer is a web based medical imaging viewer. This allows it to be used +on almost any device, anywhere. The OHIF Viewer is what is commonly referred to +as a ["Dumb Client"][simplicable] + +> A dumb client is software that fully depends on a connection to a server or +> cloud service for its functionality. Without a network connection, the +> software offers nothing useful. - [simplicable.com][simplicable] + +While the Viewer persists some data, it's scope is limited to caching things +like user preferences and previous query parameters. Because of this, the Viewer +has been built to be highly configurable to work with almost any web accessible +data source. + +![scope-of-project diagram](./../assets/img/scope-of-project.png) + +To be more specific, the OHIF Viewer is a collection of HTML, JS, and CSS files. +These can be delivered to your end users however you would like: + +- From the local network +- From a remote web server +- From a CDN (content delivery network) +- From a service-worker's cache +- etc. + +These "static asset" files are referred to collectively as a "Progressive Web +Application" (PWA), and have the same capabilities and limitations that all PWAs +have. + +All studies, series, images, imageframes, metadata, and the images themselves +must come from an external source. There are many, many ways to provide this +information, the OHIF Viewer's scope **DOES NOT** encompass providing _any_ +data; only the configuration necessary to interface with one or more of these +many data sources. The OHIF Viewer's scope **DOES** include configuration and +support for services that are protected with OpenID-Connect. + +In an effort to aid our users and contributors, we attempt to provide several +[deployment and hosting recipes](./deployment/index.md) as potential starting +points. These are not meant to be rock solid, production ready, solutions; like +most recipes, they should be augmented to best fit you and your organization's +taste, preferences, etc. + +## FAQ + +_Am I able to cache studies for offline viewing?_ + +Not currently. A web page's offline cache capabilities are limited and somewhat +volatile (mostly imposed at the browser vendor level). For more robust offline +caching, you may want to consider a server on the local network, or packaging +the OHIF Viewer as a desktop application. + +_Does the OHIF Viewer work with the local filesystem?_ + +It is possible to accomplish this through extensions; however, for a user +experience that accommodates a large number of studies, you would likely need to +package the OHIF Viewer as an [Electron app][electron]. + + + + +[simplicable]: https://simplicable.com/new/dumb-client +[electron]: https://electronjs.org/ + diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json new file mode 100644 index 00000000000..8b57449b24d --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Services", + "position": 12 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md new file mode 100644 index 00000000000..927b8b8e541 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/DicomMetadataStore.md @@ -0,0 +1,112 @@ +--- +sidebar_position: 2 +sidebar_label: DICOM Metadata Store +--- +# DICOM Metadata Store + + +## Overview +`DicomMetaDataStore` is the central location that stores the metadata in `OHIF-v3`. There +are several APIs to add study/series/instance metadata and also for getting from the store. +DataSource utilize the `DicomMetaDataStore` to add the retrieved metadata to `OHIF Viewer`. + +> In `OHIF-v3` we have significantly changed the architecture of the metadata storage to +> provide a much cleaner way of handling metadata-related tasks and services. Classes such as +> `OHIFInstanceMetadata`, `OHIFSeriesMetadata` and `OHIFStudyMetadata` has been removed and +> replaced with `DicomMetaDataStore`. +> + + +## Events +There are two events that get publish in `DicomMetaDataStore`: + + +| Event | Description | +|-----------------|------------------------------------------------------------------------------------------------| +| SERIES_ADDED | Fires when all series of one study have added their summary metadata to the `DicomMetaDataStore` | +| INSTANCES_ADDED | Fires when all instances of one series have added their metadata to the `DicomMetaDataStore` | + + +## API +Let's find out about the public API for `DicomMetaDataStore` service. + +- `EVENTS`: Object including the events mentioned above. You can subscribe to these events + by calling DicomMetaDataStore.subscribe(EVENTS.SERIES_ADDED, myFunction). [Read more about pub/sub pattern here](../pubsub.md) + +- `addInstances(instances, madeInClient = false)`: adds the instances' metadata to the store. madeInClient is explained in detail below. After + adding to the store it fires the EVENTS.INSTANCES_ADDED. + +- `addSeriesMetadata(seriesSummaryMetadata, madeInClient = false)`: adds series summary metadata. After adding it fires EVENTS.SERIES_ADDED + +- `addStudy(study)`: creates and add study-level metadata to the store. + +- `getStudy(StudyInstanceUID)`: returns the study metadata from the store. It includes all the series and instance metadata in the requested study + +- `getSeries(StudyInstanceUID, SeriesInstanceUID`: returns the series-level metadata for the requested study and series UIDs. + +- `getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID)`: returns the instance metadata based on the study, series and sop instanceUIDs. + +- `geteInstanceFromImageId`: returns the instance metadata based on the requested imageId. It searches the store for the instance that has the same imageId. + + + +### madeInClient + +Since upon adding the metadata to the store, the relevant events are fired, and there are +other services that are subscribed to these events (`HangingProtocolService` or `DisplaySetService`), sometimes +we want to add instance metadata but don't want the events to get fired. For instance, when +you are caching the data for the next study in advance, you probably don't want to trigger hanging protocol +logic, so you set `madeInClient=true` to not fire events. + + +## Storage +As discussed before, there are several API that enables getting metadata from the store and adding to the store. +However, it is good to have an understanding of where they get +stored and in what format and hierarchy. `_model` is a private variable in the store +which holds all the metadata for all studies, series, and instances, and it looks like: + + +```js title="platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js" +const _model = { + studies: [ + { + /** Study Metadata **/ + seriesLists: [ + { + // Series in study from dicom web server 1 (or different backend 1) + series: [ + { + // Series 1 Metadata + instances: [ + { + // Instance 1 metadata of Series 1 + }, + { + // Instance 2 metadata of Series 1 + }, + /** Other instances metadata **/ + ], + }, + { + // Series 2 Metadata + instances: [ + { + // Instance 1 metadata of Series 2 + }, + { + // Instance 2 metadata of Series 1 + }, + /** Other instances metadata **/ + ], + }, + ], + }, + { + // Series in study from dicom web server 2 (or different backend 2) + /** ... **/ + }, + ], + }, + ], +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md new file mode 100644 index 00000000000..f6c053cb615 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/DisplaySetService.md @@ -0,0 +1,85 @@ +--- +sidebar_position: 3 +sidebar_label: DisplaySet Service +--- +# DisplaySet Service + + +## Overview +`DisplaySetService` handles converting the `instanceMetadata` into `DisplaySet` that `OHIF` uses for the visualization. `DisplaySetService` gets initialized in the `Mode.jsx`. During the initialization `SOPClassHandlerIds` of the `modes` gets registered with the `DisplaySetService`. + +> Based on the instanceMetadata's `SOPClassHandlerId`, the correct module from the registered extensions is found by `OHIF` and its `getDisplaySetsFromSeries` runs to create a DisplaySet for the Series. + + +```js title="platform/core/src/services/DisplaySetService/DisplaySetService.js" +init(extensionManager, SOPClassHandlerIds) { + this.extensionManager = extensionManager; + this.SOPClassHandlerIds = SOPClassHandlerIds; + this.activeDisplaySets = []; +} +``` + +in `Mode.jsx` + +```js title="platform/viewer/src/routes/Mode/Mode.jsx" +export default function ModeRoute(/** ... **/) { + /** ... **/ + const { DisplaySetService } = servicesManager.services + const { sopClassHandlers } = mode + /** ... **/ + useEffect( + () => { + /** ... **/ + + // Add SOPClassHandlers to a new SOPClassManager. + DisplaySetService.init(extensionManager, sopClassHandlers) + + /** ... **/ + } + /** ... **/ + ) + /** ... **/ + return <> /** ... **/ +} +``` + + + + +## Events +There are three events that get broadcasted in `DisplaySetService`: + + + +| Event | Description | +| -------------------- | ---------------------------------------------------- | +| DISPLAY_SETS_ADDED | Fires a displayset is added to the displaysets cache | +| DISPLAY_SETS_CHANGED | Fires when a displayset is changed | +| DISPLAY_SETS_REMOVED | Fires when a displayset is removed | + + + + +## API +Let's find out about the public API for `DisplaySetService`. + +- `EVENTS`: Object including the events mentioned above. You can subscribe to these events + by calling DisplaySetService.subscribe(EVENTS.DISPLAY_SETS_CHANGED, myFunction). [Read more about pub/sub pattern here](../pubsub.md) + +- `makeDisplaySets(input, { batch, madeInClient, settings } = {}`): Creates displaySet for the provided + array of instances metadata. Each display set gets a random UID assigned. + + - `input`: Array of instances Metadata + - `batch = false`: If you need to pass array of arrays of instances metadata to have a batch creation + - `madeInClient = false`: Disables the events firing + - `settings = {}`: Hanging protocol viewport or rendering settings. For instance, setting the initial `voi`, or activating a tool upon + displaySet rendering. [Read more about hanging protocols settings here](./HangingProtocolService.md#Settings) + + +- `getDisplaySetByUID`: Returns the displaySet based on the DisplaySetUID. + +- `getDisplaySetForSOPInstanceUID`: Returns the displaySet that includes an image with the provided SOPInstanceUID + +- `getActiveDisplaySets`: Returns the active displaySets + +- `deleteDisplaySet`: Deletes the displaySets from the displaySets cache diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md new file mode 100644 index 00000000000..57587395354 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/HangingProtocolService.md @@ -0,0 +1,341 @@ +--- +sidebar_position: 4 +sidebar_label: Hanging Protocol Service +--- + +# Hanging Protocol Service + +## Overview + +`HangingProtocolService` is a migration of the `OHIF-v1` hanging protocol +engine. This service handles the arrangement of the images in the viewport. In +short, the registered protocols will get matched with the Series that are +available for the series. Each protocol gets a point, and they are ranked. The +winning protocol gets applied and its settings run for the viewports. + +You can read more about hanging protocols +[here](http://dicom.nema.org/dicom/Conf-2005/Day-2_Selected_Papers/B305_Morgan_HangProto_v1.pdf). +In short with `OHIF-v3` hanging protocols you can: + +- Define what layout of the viewport should the viewer starts with (2x2 layout) +- Define which series gets displayed in which position of the layout +- Apply certain initial viewport settings; e.g., inverting the contrast +- Enable certain tools based on what series are displayed: link prostate T2 and + ADC MRI. + +## Skeleton of A Hanging Protocol + +You can find the skeleton of the hanging protocols here: + +```js +const defaultProtocol = { + id: 'defaultProtocol', + locked: true, + hasUpdatedPriorsInformation: false, + name: 'Default', + createdDate: '2021-02-23T19:22:08.894Z', + modifiedDate: '2021-02-23T19:22:08.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + stages: [ + { + id: 'nwzau7jDkEkL8djfr', + name: 'oneByOne', + viewportStructure: { + type: 'grid', + properties: { + rows: 1, + columns: 1, + }, + }, + viewports: [ + { + viewportSettings: [], + imageMatchingRules: [], + seriesMatchingRules: [], + studyMatchingRules: [], + }, + ], + createdDate: '2021-02-23T19:22:08.894Z', + }, + ], + numberOfPriorsReferenced: -1, +}; +``` + +Let's discuss each property in depth. + +- `id`: unique identifier for the protocol + +- `protocolMatchingRules`: A list of criteria for the protocol along with the + provided points for ranking. + + - `weight`: weight for the matching rule. Eventually, all the registered + protocols get sorted based on the weights, and the winning protocol gets + applied to the viewer. + - `attriubte`: tag that needs to be matched against. This can be either + Study-level metadata or a custom attribute. + [Learn more about custom attribute matching](#custom-attribute) + + - `constraint`: the constraint that needs to be satisfied for the attribute. It accepts a `validator` which can be + [`equals`, `doesNotEqual`, `contains`, `doesNotContain`, `startsWith`, `endsWidth`] + + A sample of the matching rule is: + + ```js + { + id: 'wauZK2QNEfDPwcAQo', + weight: 1, + attribute: 'StudyInstanceUID', + constraint: { + equals: { + value: '1.3.6.1.4.1.25403.345050719074.3824.20170125112931.11', + }, + }, + required: true, + } + ``` + +- `stages`: Each protocol can define one or more stages. Each stage defines a certain layout and viewport rules. + Therefore, the `stages` property is array of objects, each object being one stage. + + - `viewportStructure`: Defines the layout of the viewer. You can define the + number of `rows` and `columns`. There should be `rows * columns` number of + viewport configuration in the `viewports` property. Note that order of + viewports are rows first then columns. + + - `viewportSettings`: custom settings to be applied to the viewport. This can + be a `voi` being applied to the viewer or a tool to get enabled. We will + discuss viewport-specific settings [below](#viewport-settings) + + - `imageMatchingRules (comming soon)`: setting the image slice for the + viewport. + + - `seriesMatchingRules`: the most important rule that matches series in the + viewport. For instance, the following stage configuration will create a + one-by-two layout and put the series whose description contains `t2` on the + left, and a series with description that contains `adc` on the right. (order + of viewports are rows, first then columns) + + ```js + stages: [ + { + id: 'hYbmMy3b7pz7GLiaT', + name: 'oneByThree', + viewportStructure: { + type: 'grid', + properties: { + rows: 1, + columns: 2, + }, + }, + viewports: [ + // viewport 1 + { + viewportSettings: [], + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'vSjk7NCYjtdS3XZAw', + weight: 1, + attribute: 'SeriesDescription', + constraint: { + contains: { + value: 't2', + }, + }, + required: false, + }, + ], + studyMatchingRules: [], + }, + // viewport 2 + { + viewportSettings: [], + imageMatchingRules: [], + seriesMatchingRules: [ + { + id: 'vSjk7NCYjtdS3XZAw', + weight: 1, + attribute: 'SeriesDescription', + constraint: { + contains: { + value: 'ADC', + }, + }, + required: true, + }, + ], + studyMatchingRules: [], + }, + ], + }, + ]; + ``` + +## Events + +There are two events that get publish in `HangingProtocolService`: + +| Event | Description | +| ------------ | -------------------------------------------------------------------- | +| NEW_LAYOUT | Fires when a new layout is requested by the `HangingProtocolService` | +| STAGE_CHANGE | Fires when the the stage is changed in the hanging protocols | + +## API + +- `getState`: returns an array: `[matchDetails, hpAlreadyApplied]`: + + - `matchDetails`: matching details for the series + - `hpAlreadyApplied`: An array which tracks whether HPServices has been + applied on each viewport. + +- `addProtocols`: adds provided protocols to the list of registered protocols + for matching + +- `run(studyMetaData, protocol)`: runs the HPService with the provided + studyMetaData and optional protocol. If protocol is not given, HP Matching + engine will search all the registered protocols for the best matching one + based on the constraints. + +- `addCustomAttribute`: adding a custom attribute for matching. (see below) + +- `addCustomViewportSetting`: adding a custom setting to a viewport (initial + `voi`). Below, we explain in detail how to add custom viewport settings via + protocol definitions. `addCustomViewportSetting` is another way to set these + settings which is exposed by API + +- + +Default initialization of the modes handles running the `HangingProtocolService` + +## Custom Attribute +In some situations, you might want to match based on a custom attribute and not the DICOM tags. For instance, +if you have assigned a `timepointId` to each study, and you want to match based on it. +Good news is that, in `OHIF-v3` you can define you custom attribute and use it for matching. + +In some situations, you might want to match based on a custom attribute and not +the DICOM tags. For instance, if you have assigned a `timepointId` to each study +and you want to match based on it. Good news is that, in `OHIF-v3` you can +define you custom attribute and use it for matching. + +There are various ways that you can let `HangingProtocolService` know of you +custom attribute. We will show how to add it inside the mode configuration. + +```js +const deafultProtocol = { + id: 'defaultProtocol', + /** ... **/ + protocolMatchingRules: [ + { + id: 'vSjk7NCYjtdS3XZAw', + weight: 3, + attribute: 'timepoint', + constraint: { + equals: { + value: 'first', + }, + }, + required: false, + }, + ], + stages: [ + /** ... **/ + ], + numberOfPriorsReferenced: -1, +}; + +// Custom function for custom attribute +const getTimePointUID = metaData => { + // requesting the timePoint Id + return myBackEndAPI(metaData); +}; + +function modeFactory() { + return { + id: 'myMode', + /** .. **/ + routes: [ + { + path: 'myModeRoute', + init: async ({}) => { + const { + DicomMetadataStore, + HangingProtocolService, + } = servicesManager.services; + + const onSeriesAdded = ({ + StudyInstanceUID, + madeInClient = false, + }) => { + const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); + + // Adding custom attribute to the hangingprotocol + HangingProtocolService.addCustomAttribute( + 'timepoint', + 'timepoint', + metaData => getFirstMeasurementSeriesInstanceUID(metaData) + ); + + HangingProtocolService.run(studyMetadata); + }; + + DicomMetadataStore.subscribe( + DicomMetadataStore.EVENTS.SERIES_ADDED, + onSeriesAdded + ); + }, + }, + ], + /** ... **/ + }; +} +``` + +## Viewport Settings + +You can define custom settings to be applied to each viewport. There are two +types of settings: + +- `viewport settings`: Currently we support two viewport settings + + - `voi`: applying an initial `voi` by setting the windowWidth and windowCenter + - `inverted`: inverting the viewport color (e.g., for PET images) + +- `props settings`: Running commands after the first render; e.g., enabling the + link tool + +Examples of each settings are : + +```js +viewportSettings: [ + { + options: { + itemId: 'SyncScroll', + interactionType: 'toggle', + commandName: 'toggleSynchronizer', + commandOptions: { toggledState: true }, + }, + commandName: 'setToolActive', + type: 'props', + }, +]; +``` + +and + +```js +viewportSettings: [ + { + options: { + voi: { + windowWidth: 400, + windowCenter: 40, + }, + }, + commandName: '', + type: 'viewport', + }, +]; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md new file mode 100644 index 00000000000..74605bce1c5 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/MeasurementService.md @@ -0,0 +1,161 @@ +--- +sidebar_position: 6 +sidebar_label: Measurement Service +--- + +# Measurement Service + +## Overview + +`MeasurementService` handles the internal measurement representation inside +`OHIF` platform. Developers can add their custom `sources` with `mappers` to +enable adding measurements inside OHIF. Currently, we are maintaining +`CornerstoneTools` annotations and corresponding mappers can be found inside the +`cornerstone` extension. However, `MeasurementService` can be configured to work +with any custom tools given that its `mappers` is added to the +`MeasurementService`. We can see the overall architecture of the +`MeasurementService` below: + +![services-measurements](../../../assets/img/services-measurements.png) + +## Events + +There are seven events that get publish in `MeasurementService`: + +| Event | Description | +| --------------------- | ------------------------------------------------------ | +| MEASUREMENT_UPDATED | Fires when a measurement is updated | +| MEASUREMENT_ADDED | Fires when a new measurement is added | +| RAW_MEASUREMENT_ADDED | Fires when a raw measurement is added (e.g., dicom-sr) | +| MEASUREMENT_REMOVED | Fires when a measurement is removed | +| MEASUREMENTS_CLEARED | Fires when all measurements are deleted | +| JUMP_TO_MEASUREMENT | Fires when a measurement is requested to be jump to | + +## API + +- `getMeasurements`: returns array of measurements + +- `getMeasurement(id)`: returns the corresponding measurement based on the + provided Id. + +- `remove(id, source)`: removes a measurement and broadcasts the + `MEASUREMENT_REMOVED` event. + +- `clearMeasurements`: removes all measurements and broadcasts + `MEASUREMENTS_CLEARED` event. + +- `createSource(name, version)`: creates a new measurement source, generates a + uid and adds it to the `sources` property of the service. + +- `addMapping(source, definition, matchingCriteria, toSourceSchema, toMeasurementSchema)`: + adds a new measurement matching criteria along with mapping functions. We will + learn more about [source/mappers below](#source--mappers) + +- `update`: updates the measurement details and fires `MEASUREMENT_UPDATED` + +- `addRawMeasurement(source,definition,data,toMeasurementSchema,dataSource = {}` + : adds a raw measurement into a source so that it may be converted to/from + annotation in the same way. E.g. import serialized data of the same form as + the measurement source. Fires `MEASUREMENT_UPDATED` or `MEASUREMENT_ADDED`. + Note that, `MeasurementService` handles finding the correct mapper upon new + measurements; however, `addRawMeasurement` provides more flexibility. You can + take a look into its usage in `dicom-sr` extension. + + - `source`: The measurement source instance. + - `definition`: The source definition you want to add the measurement to. + - `data`: The data you wish to add to the source. + - `toMeasurementSchema`: A function to get the `data` into the same shape as + the source definition. + +- `jumpToMeasurement(viewportIndex, id)`: calls the listeners who have + subscribed to `JUMP_TO_MEASUREMENT`. + +## Source / Mappers + +To create a custom measurement source and relevant mappers for each tool, you +can take a look at the `init.js` inside the `cornerstone` extension. In which we +are registering our `CornerstoneTools-v4` measurement source to +MeasurementService. Let's take a peek at the _simplified_ implementation +together. To achieve this, for each tool, we need to provide three mappers: + +- `matchingCriteria`: criteria used for finding the correct mapper for the drawn + tool. +- `toAnnotation`: tbd +- `toMeasurement`: a function that converts the tool data to OHIF internal + representation of measurement data. + +```js title="extensions/cornerstone/src/utils/measurementServiceMappings/Length.js" +function toMeasurement( + csToolsAnnotation, + DisplaySetService, + getValueTypeFromToolType +) { + const { element, measurementData } = csToolsAnnotation; + + /** ... **/ + + const { + SOPInstanceUID, + FrameOfReferenceUID, + SeriesInstanceUID, + StudyInstanceUID, + } = getSOPInstanceAttributes(element); + + const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( + SOPInstanceUID, + SeriesInstanceUID + ); + + /** ... **/ + return { + id: measurementData.id, + SOPInstanceUID, + FrameOfReferenceUID, + referenceSeriesUID: SeriesInstanceUID, + referenceStudyUID: StudyInstanceUID, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + label: measurementData.label, + description: measurementData.description, + unit: measurementData.unit, + length: measurementData.length, + type: getValueTypeFromToolType(tool), + points: getPointsFromHandles(measurementData.handles), + }; +} + +////////////////////////////////////////// + +// extensions/cornerstone/src/init.js + +const Length = { + toAnnotation, + toMeasurement, + matchingCriteria: [ + { + valueType: MeasurementService.VALUE_TYPES.POLYLINE, + points: 2, + }, + ], +}; + +const _initMeasurementService = (MeasurementService, DisplaySetService) => { + /** ... **/ + + const csToolsVer4MeasurementSource = MeasurementService.createSource( + 'CornerstoneTools', + '4' + ); + + /* Mappings */ + MeasurementService.addMapping( + csToolsVer4MeasurementSource, + 'Length', + Length.matchingCriteria, + toAnnotation, + toMeasurement + ); + + /** Other tools **/ + return csToolsVer4MeasurementSource; +}; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md new file mode 100644 index 00000000000..b080c6ef419 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/ToolbarService.md @@ -0,0 +1,216 @@ +--- +sidebar_position: 5 +sidebar_label: Toolbar Service +--- + +# Toolbar Service + +## Overview + +`ToolBarService` handles the toolbar section buttons, and what happens when a +button is clicked by the user. + +
+ +
+ +## Events + +| Event | Description | +| ----------------------- | ---------------------------------------------------------------------- | +| TOOL_BAR_MODIFIED | Fires when a button is added/removed to the toolbar | +| TOOL_BAR_STATE_MODIFIED | Fires when an interaction happens and ToolBarService state is modified | + +## API + +- `recordInteraction(interaction)`: executes the provided interaction which is + an object providing the following properties to the ToolBarService: + + - `interactionType`: can be `tool`, `toggle` and `action`. We will discuss + more each type below. + - `itemId`: tool name + - `groupId`: the Id for the tool button group; e.g., `Wwwc` which holds + presets. + - `commandName`: if tool has a command attached to run + - `commandOptions`: arguments for the command. + +- `reset`: reset the state of the toolbarService, set the primary tool to be + `Wwwc` and unsubscribe tools that have registered their functions. + +- `addButtons`: add the button definition to the service. + [See below for button definition](#button-definitions). + +- `setButtons`: sets the buttons defined in the service. It overrides all the + previous buttons + +- `getActiveTools`: returns the active tool + all the toggled-on tools + +## State + +ToolBarService has an internal state that gets updated per tool interaction and +tracks the active toolId, state of the buttons that have toggled state, and the +group buttons and which tool in each group is active. + +```js +state = { + primaryToolId: 'Wwwc', + toggles: { + /* id: true/false */ + }, + groups: { + /* track most recent click per group...*/ + }, +}; +``` + +## Interaction type + +There are three main types that a tool can have which is defined in the +interaction object. + +- `tool`: setting a tool to be active; e.g., measurement tools +- `toggle`: toggling state of a tool; e.g., viewport link (sync) +- `action`: performs a registered action outside of the ToolBarService; e.g., + capture + +A _simplified_ implementation of the ToolBarService is: + +```js +export default class ToolBarService { + /** ... **/ + recordInteraction(interaction) { + /** ... **/ + switch (interactionType) { + case 'action': { + break; + } + case 'tool': { + this.state.primaryToolId = itemId; + + commandsManager.runCommand('setToolActive', interaction.commandOptions); + break; + } + case 'toggle': { + this.state.toggles[itemId] = + this.state.toggles[itemId] === undefined + ? true + : !this.state.toggles[itemId]; + interaction.commandOptions.toggledState = this.state.toggles[itemId]; + break; + } + default: + throw new Error(`Invalid interaction type: ${interactionType}`); + } + /** ... **/ + } + /** ... **/ +} +``` + +## Button Definitions + +The simplest toolbarButtons definition has the following properties: + +![toolbarModule-zoom](../../../assets/img/toolbarModule-zoom.png) + +```js +{ + id: 'Zoom', + type: 'ohif.radioGroup', + props: { + type: 'tool', + icon: 'tool-zoom', + label: 'Zoom', + commandOptions: { toolName: 'Zoom' }, + }, +}, +``` + +| property | description | values | +| ---------------- | ----------------------------------------------------------------- | ------------------------------------------- | +| `id` | Unique string identifier for the definition | \* | +| `label` | User/display friendly to show in UI | \* | +| `icon` | A string name for an icon supported by the consuming application. | \* | +| `type` | Used to determine the button's behaviour | "tool", "toggle", "action" | +| `commandName` | (optional) The command to run when the button is used. | Any command registered by a `CommandModule` | +| `commandOptions` | (optional) Options to pass the target `commandName` | \* | + +There are three main types of toolbar buttons: + +- `tool`: buttons that enable a tool by running the `setToolActive` command with + the `commandOptions` +- `toggle`: buttons that acts as a toggle: e.g., linking viewports +- `action`: buttons that executes an action: e.g., capture button to save + screenshot + +## Nested Buttons + +You can use the `ohif.splitButton` type to build a button with extra tools in +the dropdown. + +- First you need to give your `primary` tool definition to the split button +- the `secondary` properties can be a simple arrow down (`chevron-down` icon) +- For adding the extra tools add them to the `items` list. + +You can see below how `longitudinal` mode is using the available toolbarModule +to create `MeasurementTools` nested button + +![toolbarModule-nested-buttons](../../../assets/img/toolbarModule-nested-buttons.png) + +```js title="modes/longitudinal/src/toolbarButtons.js" +{ + id: 'MeasurementTools', + type: 'ohif.splitButton', + props: { + groupId: 'MeasurementTools', + isRadio: true, + primary: { + id: 'Length', + icon: 'tool-length', + label: 'Length', + type: 'tool', + commandOptions: { + toolName: 'Length', + } + }, + secondary: { + icon: 'chevron-down', + label: '', + isActive: true, + tooltip: 'More Measure Tools', + }, + items: [ + // Length tool + { + id: 'Length', + icon: 'tool-length', + label: 'Length', + type: 'tool', + commandOptions: { + toolName: 'Length', + } + }, + // Bidirectional tool + { + id: 'Bidirectional', + icon: 'tool-bidirectional', + label: 'Length', + type: 'tool', + commandOptions: { + toolName: 'Bidirectional', + } + }, + // Ellipse tool + { + id: 'EllipticalRoi', + icon: 'tool-elipse', + label: 'Ellipse', + type: 'tool', + commandOptions: { + toolName: 'EllipticalRoi', + } + }, + ], + }, +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json new file mode 100644 index 00000000000..984ac9a4208 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Data Services", + "position": 2 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md new file mode 100644 index 00000000000..fa9128ae4da --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/data/index.md @@ -0,0 +1,53 @@ +--- +sidebar_position: 1 +sidebar_label: Overview +--- + +# Overview + +Data services are the first category of services which deal with handling non-ui +related state Each service have their own internal state which they handle. + +> We have replaced the _redux_ store. Instead, we have introduced various +> services and a pub/sub pattern to subscribe and run, which makes the `OHIF-v3` +> architecture nice and clean. + +We maintain the following non-ui Services: + +- [DicomMetadata Store](./../data/DicomMetadataStore.md) +- [DisplaySet Service](./../data/DisplaySetService.md) +- [Hanging Protocol Service](../data/HangingProtocolService.md) +- [Toolbar Service](../data/ToolBarService.md) +- [Measurement Service](../data/MeasurementService.md) + +## Service Architecture + +![services-data](../../../assets/img/services-data.png) + +> We have explained services and how to create a custom service in the +> [`ServiceManager`](../../managers/service.md) section of the docs + +To recap: The simplest service return a new object that has a `name` property, +and `Create` method which instantiate the service class. The "Factory Function" +that creates the service is provided with the implementation (this is slightly +different for UI Services). + +```js +// extensions/customExtension/src/services/backEndService/index.js +import backEndService from './backEndService'; + +export default function WrappedBackEndService(serviceManager) { + return { + name: 'myService', + create: ({ configuration = {} }) => { + return new backEndService(serviceManager); + }, + }; +} +``` + +A service, once created, can be registered with the `ServicesManager` to make it +accessible to extensions. Similarly, the application code can access named +services from the `ServicesManager`. + +[Read more of how to design a new custom service and register it](../../managers/service.md) diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/index.md new file mode 100644 index 00000000000..87d88e7ad2b --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/index.md @@ -0,0 +1,173 @@ +--- +sidebar_position: 1 +sidebar_label: Introduction +--- + +# Services + +## Overview + +Services are "concern-specific" code modules that can be consumed across layers. +Services provide a set of operations, often tied to some shared state, and are +made available to through out the app via the `ServicesManager`. Services are +particularly well suited to address [cross-cutting +concerns][cross-cutting-concerns]. + +Each service should be: + +- self-contained +- able to fail and/or be removed without breaking the application +- completely interchangeable with another module implementing the same interface + +> In `OHIF-v3` we have added multiple non-UI services and have introduced +> **pub/sub** pattern to reduce coupling between layers. +> +> [Read more about Pub/Sub](./pubsub.md) + +## Services + +The following services is available in the `OHIF-v3`. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceTypeDescription
+ + DicomMetadataStore (NEW) + + Data Service + DicomMetadataStore +
+ + DisplaySetService (NEW) + + Data Service + DisplaySetService +
+ + HangingProtocolService (NEW) + + Data Service + HangingProtocolService +
+ + MeasurementService (MODIFIED) + + Data Service + MeasurementService +
+ + ToolBarService (NEW) + + Data Service + ToolBarService +
+ + ViewportGridService (NEW) + + UI Service + ViewportGridService +
+ + Cine Service (NEW) + + UI Service + cine +
+ + UIDialogService + + UI Service + UIDialogService +
+ + UIModalService + + UI Service + UIModalService +
+ + UINotificationService + + UI Service + UINotificationService +
+ + UIViewportDialogService (NEW) + + UI Service + UIViewportDialogService +
+ + + + + +[core-services]: https://github.com/OHIF/Viewers/tree/master/platform/core/src/services +[services-manager]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/ServicesManager.js +[cross-cutting-concerns]: https://en.wikipedia.org/wiki/Cross-cutting_concern + diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md b/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md new file mode 100644 index 00000000000..4fdc8748c55 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/pubsub.md @@ -0,0 +1,120 @@ +--- +sidebar_position: 4 +sidebar_label: Pub Sub +--- + +# Pub sub + +## Overview + +Publish–subscribe pattern is a messaging pattern that is one of the fundamentals +patterns used in reusable software components. + +In short, services that implement this pattern, can have listeners subscribed +to their broadcasted events. After the event is fired, the corresponding +listener will execute the function that is registered. + +You can read more about this design pattern +[here](https://cloud.google.com/pubsub/docs/overview). + +## Example: Default Initialization + +In `Mode.jsx` we have a default initialization that demonstrates a series of +subscriptions to various events. + +```js +async function defaultRouteInit({ + servicesManager, + studyInstanceUIDs, + dataSource, +}) { + const { + DisplaySetService, + HangingProtocolService, + } = servicesManager.services; + + const unsubscriptions = []; + + const { + unsubscribe: instanceAddedUnsubscribe, + } = DicomMetadataStore.subscribe( + DicomMetadataStore.EVENTS.INSTANCES_ADDED, + ({ StudyInstanceUID, SeriesInstanceUID, madeInClient = false }) => { + const seriesMetadata = DicomMetadataStore.getSeries( + StudyInstanceUID, + SeriesInstanceUID + ); + + DisplaySetService.makeDisplaySets(seriesMetadata.instances, madeInClient); + } + ); + + unsubscriptions.push(instanceAddedUnsubscribe); + + studyInstanceUIDs.forEach(StudyInstanceUID => { + dataSource.retrieve.series.metadata({ StudyInstanceUID }); + }); + + const { unsubscribe: seriesAddedUnsubscribe } = DicomMetadataStore.subscribe( + DicomMetadataStore.EVENTS.SERIES_ADDED, + ({ StudyInstanceUID }) => { + const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); + HangingProtocolService.run(studyMetadata); + } + ); + unsubscriptions.push(seriesAddedUnsubscribe); + + return unsubscriptions; +} +``` + +## Unsubscription + +You need to be careful if you are adding custom subscriptions to the app. Each +subscription will return an unsubscription function that needs to be executed on +component destruction to avoid adding multiple subscriptions to the same +observer. + +Below, we can see `simplified` `Mode.jsx` and the corresponding `useEffect` +where the unsubscription functions are executed upon destruction. + +```js title="platform/viewer/src/routes/Mode/Mode.jsx" +export default function ModeRoute(/**..**/) { + /**...**/ + useEffect(() => { + /**...**/ + + DisplaySetService.init(extensionManager, sopClassHandlers); + + extensionManager.onModeEnter(); + mode?.onModeEnter({ servicesManager, extensionManager }); + + hangingProtocols.forEach(extentionProtocols => { + const { protocols } = extensionManager.getModuleEntry(extentionProtocols); + HangingProtocolService.addProtocols(protocols); + }); + + const setupRouteInit = async () => { + if (route.init) { + return await route.init(/**...**/); + } + + return await defaultRouteInit(/**...**/); + }; + + let unsubscriptions; + setupRouteInit().then(unsubs => { + unsubscriptions = unsubs; + }); + + return () => { + extensionManager.onModeExit(); + mode?.onModeExit({ servicesManager, extensionManager }); + unsubscriptions.forEach(unsub => { + unsub(); + }); + }; + }); + return <> /**...**/ ; +} +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json b/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json new file mode 100644 index 00000000000..9c012134eb0 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "UI Services", + "position": 3 +} diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md new file mode 100644 index 00000000000..707b5d97f7f --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/cine-service.md @@ -0,0 +1,8 @@ +--- +sidebar_position: 7 +sidebar_label: CINE Service +--- + +# CINE Service + +TODO... diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md new file mode 100644 index 00000000000..5ba4a539057 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/index.md @@ -0,0 +1,304 @@ +--- +sidebar_position: 1 +sidebar_label: Overview +--- + +# Overview + + + +A typical web application will have components and state for common UI like +modals, notifications, dialogs, etc. A UI service makes it possible to leverage +these components from an extension. + +We maintain the following UI Services: + +- [UI Notification Service](ui-notification-service.md) +- [UI Modal Service](ui-modal-service.md) +- [UI Dialog Service](ui-dialog-service.md) +- [UI Viewport Dialog Service](ui-viewport-dialog-service.md) +- [CINE Service](cine-service.md) +- [Viewport Grid Service](viewport-grid-service.md) + + + +![UIService](../../../assets/img/ui-services.png) + + + +## Providers for UI services + +**There are several context providers that wraps the application routes. This +makes the context values exposed in the app, and service's `setImplementation` +can get run to override the implementation of the service.** + +```js title="platform/viewer/src/App.jsx" +function App({ config, defaultExtensions }) { + /**...**/ + /**...**/ + return ( + /**...**/ + + + + + + + {appRoutes} + + + + + + + /**...**/ + ); +} +``` + +## Example + +For instance `UIModalService` has the following Public API: + +```js title="platform/core/src/services/UIModalService/index.js" +const publicAPI = { + name, + hide: _hide, + show: _show, + setServiceImplementation, +}; + +function setServiceImplementation({ + hide: hideImplementation, + show: showImplementation, +}) { + /** ... **/ + serviceImplementation._hide = hideImplementation; + serviceImplementation._show = showImplementation; + /** ... **/ +} + +export default { + name: 'UIModalService', + create: ({ configuration = {} }) => { + return publicAPI; + }, +}; +``` + +`UIModalService` implementation can be set (override) in its context provider. +For instance in `ModalProvider` we have: + +```js title="platform/ui/src/contextProviders/ModalProvider.jsx" +import { Modal } from '@ohif/ui'; + +const ModalContext = createContext(null); +const { Provider } = ModalContext; + +export const useModal = () => useContext(ModalContext); + +const ModalProvider = ({ children, modal: Modal, service }) => { + const DEFAULT_OPTIONS = { + content: null, + contentProps: null, + shouldCloseOnEsc: true, + isOpen: true, + closeButton: true, + title: null, + customClassName: '', + }; + + const show = useCallback(props => setOptions({ ...options, ...props }), [ + options, + ]); + + const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [ + DEFAULT_OPTIONS, + ]); + + useEffect(() => { + if (service) { + service.setServiceImplementation({ hide, show }); + } + }, [hide, service, show]); + + const { + content: ModalContent, + contentProps, + isOpen, + title, + customClassName, + shouldCloseOnEsc, + closeButton, + } = options; + + return ( + + {ModalContent && ( + + + + )} + {children} + + ); +}; + +export default ModalProvider; + +export const ModalConsumer = ModalContext.Consumer; +``` + +Therefore, anywhere in the app that we have access to react context we can use +it by calling the `useModal` from `@ohif/ui`. As a matter of fact, we are +utilizing the modal for the preference window which shows the hotkeys after +clicking on the gear button on the right side of the header. + +A `simplified` code for our worklist is: + +```js title="platform/viewer/src/routes/WorkList/WorkList.jsx" +import { useModal, Header } from '@ohif/ui'; + +function WorkList({ + history, + data: studies, + dataTotal: studiesTotal, + isLoadingData, + dataSource, + hotkeysManager, +}) { + const { show, hide } = useModal(); + + /** ... **/ + + const menuOptions = [ + { + title: t('Header:About'), + icon: 'info', + onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer' }), + }, + { + title: t('Header:Preferences'), + icon: 'settings', + onClick: () => + show({ + title: t('UserPreferencesModal:User Preferences'), + content: UserPreferences, + contentProps: { + hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions( + hotkeyDefaults + ), + hotkeyDefinitions, + onCancel: hide, + currentLanguage: currentLanguage(), + availableLanguages, + defaultLanguage, + onSubmit: state => { + i18n.changeLanguage(state.language.value); + hotkeysManager.setHotkeys(state.hotkeyDefinitions); + hide(); + }, + onReset: () => hotkeysManager.restoreDefaultBindings(), + }, + }), + }, + ]; + /** ... **/ + return ( +
+ /** ... **/ +
+ /** ... **/ +
+ ); +} +``` + + + + + + + +## Tips & Tricks + +It's important to remember that all we're doing is making it possible to control +bits of the application's UI from an extension. Here are a few non-obvious +takeaways worth mentioning: + +- Your application code should continue to use React context + (consumers/providers) as it normally would +- You can substitute our "out of the box" UI implementations with your own +- You can create and register your own UI services +- You can choose not to register a service or provide a service implementation +- In extensions, you can provide fallback/alternative behavior if an expected + service is not registered + - No `UIModalService`? Use the `UINotificationService` to notify users. +- You can technically register a service in an extension and expose it to the + core application + +> Note: These are recommended patterns, not hard and fast rules. Following them +> will help reduce confusion and interoperability with the larger OHIF +> community, but they're not silver bullets. Please speak up, create an issue, +> if you would like to discuss new services or improvements to this pattern. diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md new file mode 100644 index 00000000000..152841ae625 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-dialog-service.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 4 +sidebar_label: UI Dialog Service +--- +# UI Dialog Service + +Dialogs have similar characteristics to that of Modals, but often with a +streamlined focus. They can be helpful when: + +- We need to grab the user's attention +- We need user input +- We need to show additional information + +If you're curious about the DOs and DON'Ts of dialogs and modals, check out this +article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] + + + +## Interface + +For a more detailed look on the options and return values each of these methods +is expected to support, [check out it's interface in `@ohif/core`][interface] + +| API Member | Description | +| -------------- | ------------------------------------------------------ | +| `create()` | Creates a new Dialog that is displayed until dismissed | +| `dismiss()` | Dismisses the specified dialog | +| `dismissAll()` | Dismisses all dialogs | + +## Implementations + +| Implementation | Consumer | +| ------------------------------------ | -------------------------- | +| [Dialog Provider][dialog-provider]\* | Baked into Dialog Provider | + +`*` - Denotes maintained by OHIF + +> 3rd Party implementers may be added to this table via pull requests. + + + + +[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UIDialogService/index.js +[dialog-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/DialogProvider.js +[ux-article]: https://uxplanet.org/best-practices-for-modals-overlays-dialog-windows-c00c66cddd8c + diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md new file mode 100644 index 00000000000..cb36d7e6cf6 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-modal-service.md @@ -0,0 +1,56 @@ +--- +sidebar_position: 3 +sidebar_label: UI Modal Service +--- +# UI Modal Service + +Modals have similar characteristics to that of Dialogs, but are often larger, +and only allow for a single instance to be viewable at once. They also tend to +be centered, and not draggable. They're commonly used when: + +- We need to grab the user's attention +- We need user input +- We need to show additional information + +If you're curious about the DOs and DON'Ts of dialogs and modals, check out this +article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] + + +
+ +
+ +## Interface + +For a more detailed look on the options and return values each of these methods +is expected to support, [check out it's interface in `@ohif/core`][interface] + +| API Member | Description | +| ---------- | ------------------------------------- | +| `hide()` | Hides the open modal | +| `show()` | Shows the provided content in a modal | + +## Implementations + +| Implementation | Consumer | +| ---------------------------------- | --------- | +| [Modal Provider][modal-provider]\* | Modal.jsx | + +`*` - Denotes maintained by OHIF + + + + + +> 3rd Party implementers may be added to this table via pull requests. + + + + +[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UIModalService/index.js +[modal-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/ModalProvider.js +[modal-consumer]: https://github.com/OHIF/Viewers/tree/master/platform/ui/src/components/ohifModal +[ux-article]: https://uxplanet.org/best-practices-for-modals-overlays-dialog-windows-c00c66cddd8c + diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md new file mode 100644 index 00000000000..96185943482 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-notification-service.md @@ -0,0 +1,55 @@ +--- +sidebar_position: 2 +sidebar_label: UI Notification Service +--- +# UI Notification Service + +Notifications can be annoying and disruptive. They can also deliver timely +helpful information, or expedite the user's workflow. Here is some high level +guidance on when and how to use them: + +- Notifications should be non-interfering (timely, relevant, important) +- We should only show small/brief notifications +- Notifications should be contextual to current behavior/actions +- Notifications can serve warnings (acting as a confirmation) + +If you're curious about the DOs and DON'Ts of notifications, check out this +article: ["How To Design Notifications For Better UX"][ux-article] + + + +
+ +
+ + +## Interface + +For a more detailed look on the options and return values each of these methods +is expected to support, [check out it's interface in `@ohif/core`][interface] + +| API Member | Description | +| ---------- | --------------------------------------- | +| `hide()` | Hides the specified notification | +| `show()` | Creates and displays a new notification | + +## Implementations + +| Implementation | Consumer | +| ---------------------------------------- | ----------------------------------------- | +| [Snackbar Provider][snackbar-provider]\* | [SnackbarContainer][snackbar-container]\* | + +`*` - Denotes maintained by OHIF + +> 3rd Party implementers may be added to this table via pull requests. + + + + +[interface]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/services/UINotificationService/index.js +[snackbar-provider]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/contextProviders/SnackbarProvider.js +[snackbar-container]: https://github.com/OHIF/Viewers/blob/master/platform/ui/src/components/snackbar/SnackbarContainer.js +[ux-article]: https://uxplanet.org/how-to-design-notifications-for-better-ux-6fb0711be54d + diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md new file mode 100644 index 00000000000..7370428459d --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/ui-viewport-dialog-service.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 5 +sidebar_label: UI Viewport Dialog Service +--- + +# UI Viewport Dialog Service + +## Overview +This is a new UI service, that creates a modal inside the viewport. + +Dialogs have similar characteristics to that of Modals, but often with a +streamlined focus. They can be helpful when: + +- We need to grab the user's attention +- We need user input +- We need to show additional information + +If you're curious about the DOs and DON'Ts of dialogs and modals, check out this +article: ["Best Practices for Modals / Overlays / Dialog Windows"][ux-article] + + + +
+ +
+ +## Interface + +For a more detailed look on the options and return values each of these methods +is expected to support, [check out it's interface in `@ohif/core`][interface] + +| API Member | Description | +| -------------- | ------------------------------------------------------ | +| `create()` | Creates a new Dialog that is displayed until dismissed | +| `dismiss()` | Dismisses the specified dialog | +| `dismissAll()` | Dismisses all dialogs | + +## Implementations + +| Implementation | Consumer | +| ------------------------ | -------------------------- | +| [ViewportDialogProvider] | Baked into Dialog Provider | + +`*` - Denotes maintained by OHIF + + +## State + +```js +const DEFAULT_STATE = { + viewportIndex: null, + message: undefined, + type: 'info', // "error" | "warning" | "info" | "success" + actions: undefined, // array of { type, text, value } + onSubmit: () => { + console.log('btn value?'); + }, + onOutsideClick: () => { + console.warn('default: onOutsideClick') + }, + onDismiss: () => { + console.log('dismiss? -1'); + }, +}; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md b/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md new file mode 100644 index 00000000000..d48ab4e00e5 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/services/ui/viewport-grid-service.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 6 +sidebar_label: Viewport Grid Service +--- + +# Viewport Grid Service + +## Overview + +This is a new UI service, that handles the grid layout of the viewer. + +## Interface + +For a more detailed look on the options and return values each of these methods +is expected to support, [check out it's interface in `@ohif/core`][interface] + +| API Member | Description | +| --------------------------------------------------------------------- | --------------------------------------------------- | +| `setActiveViewportIndex(index)` | Sets the active viewport index in the app | +| `getState()` | Gets the states of the viewport (see below) | +| `setDisplaySetsForViewport({ viewportIndex, displaySetInstanceUID })` | Sets displaySet for viewport based on displaySet Id | +| `setLayout({numCols, numRows})` | Sets rows and columns | +| `reset()` | Resets the default states | + +## Implementations + +| Implementation | Consumer | +| ---------------------- | -------------------------- | +| [ViewportGridProvider] | Baked into Dialog Provider | + +`*` - Denotes maintained by OHIF + +## State + +```js +const DEFAULT_STATE = { + // starting from null, hanging + // protocol will defined number of rows and cols + numRows: null, + numCols: null, + viewports: [ + /* + * { + * displaySetInstanceUID: string, + * } + */ + ], + activeViewportIndex: 0, +}; +``` diff --git a/platform/docs/versioned_docs/version-3.0/platform/themeing.md b/platform/docs/versioned_docs/version-3.0/platform/themeing.md new file mode 100644 index 00000000000..48a881c20a7 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/platform/themeing.md @@ -0,0 +1,166 @@ +--- +sidebar_position: 2 +sidebar_label: Theming +--- + +# Viewer: Theming + +`OHIF-v3` has introduced the +[`LayoutTemplateModule`](./extensions/modules/layout-template.md) which enables +addition of custom layouts. You can easily design your custom components inside +an extension and consume it via the layoutTemplate module you write. + +## Tailwind CSS + +[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for +creating custom user interfaces. + +Below you can see a compiled version of the tailwind configs. Each section can +be edited accordingly. For instance screen size break points, primary and +secondary colors, etc. + +```js +module.exports = { + prefix: '', + important: false, + separator: ':', + theme: { + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + }, + colors: { + overlay: 'rgba(0, 0, 0, 0.8)', + transparent: 'transparent', + black: '#000', + white: '#fff', + initial: 'initial', + inherit: 'inherit', + + indigo: { + dark: '#0b1a42', + }, + aqua: { + pale: '#7bb2ce', + }, + + primary: { + light: '#5acce6', + main: '#0944b3', + dark: '#090c29', + active: '#348cfd', + }, + + secondary: { + light: '#3a3f99', + main: '#2b166b', + dark: '#041c4a', + active: '#1f1f27', + }, + + common: { + bright: '#e1e1e1', + light: '#a19fad', + main: '#fff', + dark: '#726f7e', + active: '#2c3074', + }, + + customgreen: { + 100: '#05D97C', + }, + + customblue: { + 100: '#c4fdff', + 200: '#38daff', + }, + }, + }, +}; +``` + +You can also use the color variable like before. For instance: + +```js +primary: { + default: ‘var(--default-color)‘, + light: ‘#5ACCE6’, + main: ‘#0944B3’, + dark: ‘#090C29’, + active: ‘#348CFD’, +} +``` + +## White Labeling + +A white-label product is a product or service produced by one company (the +producer) that other companies (the marketers) rebrand to make it appear as if +they had made it - +[Wikipedia: White-Label Product](https://en.wikipedia.org/wiki/White-label_product) + +Current white-labeling options are limited. We expose the ability to replace the +"Logo" section of the application with a custom "Logo" component. You can do +this by adding a whiteLabeling key to your configuration file. + +```js +window.config = { + /** .. **/ + whiteLabeling: { + createLogoComponentFn: function(React) { + return React.createElement( + 'a', + { + target: '_blank', + rel: 'noopener noreferrer', + className: 'text-white underline', + href: 'http://radicalimaging.com', + }, + React.createElement('h5', {}, 'RADICAL IMAGING') + ); + }, + }, + /** .. **/ +}; +``` + +> You can simply use the stylings from tailwind CSS in the whiteLabeling + +In addition to text, you can also add your custom logo + +```js +window.config = { + /** .. **/ + whiteLabeling: { + createLogoComponentFn: function(React) { + return React.createElement( + 'a', + { + target: '_self', + rel: 'noopener noreferrer', + className: 'text-purple-600 line-through', + href: '/', + }, + React.createElement('img', { + src: './customLogo.svg', + // className: 'w-8 h-8', + }) + ); + }, + }, + /** .. **/ +}; +``` + +The output will look like + +![custom-logo](../assets/img/custom-logo.png) + + + + +[wikipedia]: https://en.wikipedia.org/wiki/White-label_product + diff --git a/platform/docs/versioned_docs/version-3.0/release-notes.md b/platform/docs/versioned_docs/version-3.0/release-notes.md new file mode 100644 index 00000000000..242221bd7d9 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/release-notes.md @@ -0,0 +1,141 @@ +--- +sidebar_position: 2 +sidebar_label: Release Notes +--- + +# Release Notes + +> New `OHIF-v3` architecture has made OHIF a general purpose extensible medical +> imaging **platform**, as opposed to a configurable viewer. + +## What's new in `OHIF-v3` + +`OHIF-v3` is our second try for a React-based viewer, and is the third version +of our medical image web viewers from the start. The summary of changes includes: + +- Addition of workflow modes + - Often, medical imaging use cases involves lots of specific workflows that + re-use functionalities. We have added the capability of workflow modes, that + enable people to customize user interface and configure application for + specific workflow. + - The idea is to re-use the functionalities that extensions provide and create + a workflow. Brain segmentation workflow is different from prostate + segmentation in UI for sure; however, they share the segmentation tools that + can be re-used. + - Our vision is that technical people focus of developing extensions which + provides core functionalities, and experts to build modes by picking the + appropriate functionalities from each extension. + +* UI has been completely redesigned with modularity and workflow modes in mind. +* New UI components have been built with Tailwind CSS +* Redux store has been removed from the viewer in favour of services backed by + React's Context API + +Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OHIF-v2 functionalitiesOHIF-v3Comment
Rendering of 2D images via Cornerstone
Study List
Series Browser
DICOM JSON
2D Tools via CornerstoneTools
OpenID Connect standard authentication flow for connecting to identity providers
Internationalization
Drag/drop DICOM data into the viewer (see https://viewer.ohif.org/local)
White-labelling: Easily replace the OHIF Logo with your logo
DICOM Whole-slide imaging viewport🔜In Progress
IHE Invoke Image Display - Standard-compliant launching of the viewer (e.g. from PACS or RIS)🔜Not Started
DICOM PDF support🔜Not Started
Displaying non-renderable DICOM as HTML🔜Not Started
Segmentation support🔜Not Started
RT STRUCT support🔜Not Started
DICOM upload to PACS🔜Not Started
Google Cloud adapter🔜Not Started
VTK Extension + MIP / MPR layoutOther plans that involves amazing news soon!
UMD Build (Embedded Viewer). The problem is that this breaks a bunch of extensions that rely on third party scripts (e.g. VTK) which have their own web worker loaders.
diff --git a/platform/docs/versioned_docs/version-3.0/resources.md b/platform/docs/versioned_docs/version-3.0/resources.md new file mode 100644 index 00000000000..be7b10ed756 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/resources.md @@ -0,0 +1,101 @@ +--- +sidebar_position: 9 +sidebar_label: Resources +--- + +# Resources + +Throughout the development of the OHIF Viewer, we have participated in various +conferences and "hackathons". In this page, we will provide the presentations +and other resources that we have provided to the community in the past: + +## 2022 + +### [NA-MIC Project Week 36th 2022 - Remote](https://github.com/NA-MIC/ProjectWeek/blob/master/PW36_2022_Virtual/README.md) + +The Project Week is a week-long hackathon of hands-on activity in which medical +image computing researchers. OHIF team participated and gave a talk on OHIF and +Cornerstone in the 36th Project Week: +[[Slides]](https://docs.google.com/presentation/d/1-GtOKmr2cQi-r3OFyseSmgLeurtB3KXUkGMx2pVLh1I/edit?usp=sharing) +[[Video]](https://vimeo.com/668339696/63a2c48de8) + +## 2021 + +### [NA-MIC Project Week 35th 2021 - Remote](https://github.com/NA-MIC/ProjectWeek/tree/master/PW35_2021_Virtual) + +The Project Week is a week-long hackathon of hands-on activity in which medical +image computing researchers. OHIF team participated in the 35th Project Week +in 2021. +[[Slides]](https://docs.google.com/presentation/d/1KYNjuiI8lT1foQ4P9TGNV0lBhM6H-5KBs0wkYj4JJbk/edit?usp=sharing) + +### Chan Zuckerberg Initiative (CZI) + +Project presentations and demonstrations of Essential Open Source Software for +Science (EOSS) grantees +[[Slides]](https://docs.google.com/presentation/d/1_CLtG2hsL3ZxOtV2olVnzBOzq-TMLrHLomOy3FiU4NE/edit?usp=sharing) +[[Video]](https://youtu.be/0FjKkTJO0Rc?t=3737) + +### Google Cloud Tech + +Healthcare Imaging with Cloud Healthcare API +[[Video]](https://www.youtube.com/watch?v=2MiX9ScHFhY) + +## 2020 + +### OHIF ITCR Pitch + +OHIF pitch for Informatics Technology for Cancer Research (ITCR) +[[Slides]](https://docs.google.com/presentation/d/1MZXnZrVAnjmhVIWqC-aRSvJOoMMRLhLddACdCa1TybM/edit?usp=sharing) +[[Video]](https://vimeo.com/678769373/625bdb8793) + +## 2019 + +### OHIF and VTK.js Training Course + +OHIF and Kitware collaboration to create a training course for OHIF and VTK.js +developers. Funding for this work was provided by Kitware (NIH NINDS +R44NS081792, NIH NINDS R42NS086295, NIH NIBIB and NIGMS R01EB021396, NIH NIBIB +R01EB014955), Isomics (NIH P41 EB015902), and Massachusetts General Hospital +(NIH U24 CA199460). + +1. Introduction to VTK.js and OHIF + [[Slides]](https://docs.google.com/presentation/d/1NCJxpfx_qUGJI_2DhbECzaOg0k-Z6b65QlUptCofN-A/edit#slide=id.p) + [[Video]](https://vimeo.com/375520781) +2. Developing with VTK.js + [[Slides]](https://docs.google.com/presentation/d/17TCS6EhFi6SWFIrcAJ-DFdFzFFL-WD9BBTv-owmMdDU/edit#slide=id.p) + [[Video]](https://vimeo.com/375521036) +3. VTK.js Architecture and Tooling + [[Slides]](https://docs.google.com/presentation/d/1Sr1OGxMSw0oCt46koKQbmwSIE11Kqq8MGtyW3W0ASpk/edit?usp=gmail_thread) + [[Video]](https://vimeo.com/375521810) +4. OHIF + VTK.js Integration + [[Slides]](https://docs.google.com/presentation/d/1Iwg-u01HGVf1CgC6NbcBD3gm3uHN9WhjU59FSz55TN8/edit?ts=5d9c9ce4#slide=id.g59aa99cda4_0_131) + [[Video]](https://vimeo.com/375521206) + +## 2017 + +### Lesion Tracker + +LesionTracker: Extensible Open-Source Zero-Footprint Web Viewer for Cancer +Imaging Research and Clinical Trials. This project was supported in part by +grant U24 CA199460 from the National Cancer Institute (NCI) Informatics +Technology for Cancer Research (ITCR) Program. +[[Video]](https://www.youtube.com/watch?v=gUIPtoSBL-Q) + +### OHIF Community Meeting - June + +[[Slides]](https://docs.google.com/presentation/d/1K9Y6eP5DYTXoDlfwCZE6GkCUp83AK4_40YQS0dlzVBo/edit?usp=sharing) + +## 2016 + +### Imaging Community Call + +Open Source Oncology Web Viewer; Presentation by Gordon J. Harris +[[Slides]](https://www.slideshare.net/imgcommcall/lesiontracker) + +### OHIF Community Meeting - June + +[[Slides]](https://docs.google.com/presentation/d/1Ai25mBG0ZWUPhaadp3VnbCVmkYs9K51sQ8osMixrvJ0/edit?usp=sharing) + +### OHIF Community Meeting - September + +[[Slides]](https://docs.google.com/presentation/d/1iYZoU7v7KHSLHiKwH1_9_wweAkG7RGnyxrWeeHva4zQ/edit?usp=sharing) diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json b/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json new file mode 100644 index 00000000000..68ea78e7844 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "User Guide", + "position": 2 +} diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/index.md b/platform/docs/versioned_docs/version-3.0/user-guide/index.md new file mode 100644 index 00000000000..52f44bad8cb --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/index.md @@ -0,0 +1,83 @@ +--- +sidebar_position: 2 +sidebar_label: Study List +--- + +# Study List + +## Overview + +The first page you will see when the viewer is loaded is the `Study List`. In +this page you can explore all the studies that are stored on the configured +server for the `OHIF Viewer`. + +![user-study-list](../assets/img/user-study-list.png) + +## Sorting + +When the Study List is opened, the application queries the PACS for 101 studies +by default. If there are greater than 100 studies returned, the default sort for +the study list is dictated by the image archive that hosts these studies for the +viewer and study list sorting will be disabled. If there are less than or equal +to 100 studies returned, they will be sorted by study date (most recent to +oldest) and study list sorting will be enabled. Whenever a query returns greater +than 100 studies, use filters to narrow results below 100 studies to enable +Study List sorting. + +## Filters + +There are certain filters that can be used to limit the study list to the +desired criteria. + +- Patient Name: Searches between patients names +- MRN: Searches between patients Medical Record Number +- Study Date: Filters the date of the acquisition +- Description: Searches between study descriptions +- Modality: Filters the modalities +- Accession: Searches between patients accession number + +An example of using study list filter is shown below: + +![user-study-filter](../assets/img/user-study-filter.png) + +Below the study list are pagination options for 25, 50, or 100 studies per page. + +![user-study-next](../assets/img/user-study-next.png) + +## Study Summary + +Click on a study to expand the study summary panel. + +![user-study-summary](../assets/img/user-study-summary.png) + +A summary of series available in the study is shown, which contains the series +description, series number, modality of the series, instances in the series, and +buttons to launch viewer modes to display the study. + +## Study Specific Modes + +All available modes are seen in the study expanded view. Modes can be enabled or +disabled for a study based on the modalities contained within the study. + +In the screenshot below, there are two modes shown for the selected study + +- Basic Viewer: Default mode that enables rendering and measurement tracking + +- PET/CT Fusion: Mode for visualizing the PET CT study in a 3x3 format. + +Based on the mode configurations (e.g., available modalities), PET/CT mode is +disabled for studies that do not contain PET AND CT images. + + + +![user-studyist-modespecific](../assets/img/user-studyist-modespecific.png) + +The previous screenshot shows a study containing PET and CT images and both +Basic Viewer and PET/CT Mode are available. + +## View Study + +The `Basic Viewer` mode is available for all studies by default. Click on the +mode button to launch the viewer. + +![user-open-viewer](../assets/img/user-open-viewer.png) diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md new file mode 100644 index 00000000000..9032ecc5f62 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/Language.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 8 +--- + +# Language + +OHIF supports internationalization capabilities and setting the general language +of the Viewer. + +It should be noted that we don't have complete translations for all the components +and all the languages; however, you can easily add the key value translation pairs +following developer guides. + +Summary of language changing usage can be seen below: + + + +## Overview Video + +
+ +
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json new file mode 100644 index 00000000000..417861dba3d --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Basic Viewer", + "position": 2 +} diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md new file mode 100644 index 00000000000..5d8aac1fb47 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/hotkeys.md @@ -0,0 +1,15 @@ +--- +sidebar_position: 7 +--- + +# Hotkeys + +To open the hotkey assignment panel, you can click on the Preferences gear on the +top right side of the viewer. + + +Below, you can see the default hotkeys key bindings: + +![user-hotkeys-default](../../assets/img/user-hotkeys-default.png) + +Hotkeys can be assigned to custom bindings that persist for the duration of the browser session. diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md new file mode 100644 index 00000000000..6918ce3b1b8 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/index.md @@ -0,0 +1,29 @@ +--- +sidebar_position: 1 +sidebar_label: Overview +--- + + +# Overview +When you open a mode, viewport, toolbar and panels of the mode get shown. +It is important to note that each mode has a different UI, which serves its purpose. +Here we explain various components of `Basic Viewer` mode which includes measurement +tracking functionalities. + +Basic viewer mode (longitudinal): + +![user-viewer](../../assets/img/user-viewer.png) + +Let's break different aspects of the viewer to the main components: + +- Left Panel (study panel): displays series thumbnails with series details +- Viewport: renders the image and displays annotations +- Right Panel (measurements): displays annotations details +- Toolbar: displays tools and logo + +![user-viewer-components](../../assets/img/overview.png) + + + + +Now, we explain each component and its sub-elements in detail. diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md new file mode 100644 index 00000000000..b760bf37e46 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-panel.md @@ -0,0 +1,74 @@ +--- +sidebar_position: 3 +--- + +# Measurement Panel + +## Introduction +In `Basic Viewer` mode, the right panel is the `Measurement Panel`. The Measurement Panel can be expanded or hidden by clicking on the arrow to the left of `Measurements`. + +Select a measurement tool and mark an image to initiate measurement tracking. A pop-up will ask if you want to track measurements for the series on which the annotation was drawn. + +![user-measurement-panel-modal](../../assets/img/measurement-panel-prompt.png) + + + + + +If you select `Yes`, the series becomes a `tracked series`, and the current drawn measurement and next measurements are shown on the measurement panel on the right. + +![user-measurement-panel-tracked](../../assets/img/measurement-panel-tracked.png) + +If you select `No`, the measurement becomes temporary. The next annotation made will repeat the measurement tracking prompt. + +If you select `No, do not ask again`, all annotations made on the study will be temporary. + +![measurement-temporary](../../assets/img/measurement-temporary.png) + + +## Labeling Measurements +You can edit the measurement name by hovering over the measurement and selecting the edit icon. You can also label or relabel a measurement by right-clicking on it in the viewport. + +![user-measurement-edit](../../assets/img/measurement-panel-1.png) + + + +## Deleting a Measurement +A measurement can be deleting by dragging it outside the image in the viewport or by right-clicking on the measurement in the viewport and selecting 'Delete'. + + +## Jumping to a Measurement +Measurement navigation inside the top viewport can be used to move to previous and next measurement. + + +![measurements-prevNext](../../assets/img/measurements-prevNext.png) + +If a series containing a measurement is currently being displayed in a viewport, you can jump to display the measurement in the viewport by clicking on it in the Measurement Panel. + +## Export Measurements + +You can export the measurements by clicking on the `Export`. A CSV file will get downloaded to your local computer containing the drawn measurements. + + +![user-measurement-export](../../assets/img/user-measurement-export.png) + + +If you have set up your DICOM server to be able to store instances from the viewer, then you are able to create a report by clicking on the `Create Report`. +This will create a DICOM Structured Report (SR) from the measurements and push it +to the server. + +For instance, running the Viewer on a local DCM4CHEE: + + + +
+ +
+ +## Overview Video +An overview of measurement drawing and exporting can be seen below: + + +
+ +
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md new file mode 100644 index 00000000000..a15f7e5dfb6 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/measurement-tracking.md @@ -0,0 +1,124 @@ +--- +sidebar_position: 4 +--- + +# Measurement Tracking + +## Introduction +OHIF-V3's `Basic Viewer` implements a `Measurement Tracking` workflow. Measurement +tracking allows you to: + +- Draw annotations and have them shown in the measurement panel +- Create a report from the tracked measurement and export them as DICOM SR +- Use already exported DICOM SR to re-hydrate the measurements in the viewer + + +## Status Icon +Each viewport has a left icon indicating whether the series within the viewport +contains: + +- tracked measurement OR +- untracked measurement OR +- Structured Report OR +- Locked (uneditable) Structured Report + +In the following, we will discuss each category. + +### Tracked vs Untracked Measurements + +`OHIF-v3` implements a workflow for measurement tracking that can be seen below. + +![user-measurement-panel-modal](../../assets/img/tracking-workflow1.png) + +In summary, when you create an annotation, a prompt will be shown whether to start tracking or not. If you start the tracking, the annotation style will change to a solid line, and annotation details get displayed on the measurement panel. +On the other hand, if you decline the tracking prompt, the measurement will be considered "temporary," and annotation style remains as a dashed line and not shown on the right panel, and cannot be exported. + + +Below, you can see different icons that appear for a tracked vs. untracked series in +`OHIF-v3`. + +![tracked-not-tracked](../../assets/img/tracked-not-tracked.png) + + + +#### Overview video for starting the tracking for measurements: + + +
+ +
+ + +

+ +#### Overview video for not starting tracking for measurements: + + +
+ +
+ + +### Reading and Writing DICOM SR + +`OHIF-v3` provides full support for reading, writing and mapping the DICOM Structured +Report (SR) to interactable `Cornerstone Tools`. When you load an already exported +DICOM SR into the viewer, you will be prompted whether to track the measurements +for the series or not. + +![SR-exported](../../assets/img/SR-exported.png) + +If you click Yes, DICOM SR measurements gets re-hydrated into the viewer and +the series become a tracked series. However, If you say no and later decide to say track the measurements, you can always click on the SR button that will prompt you +with the same message again. + +![restore-exported-sr](../../assets/img/restore-exported-sr.png) + +The full workflow for saving measurements to SR and loading SR into the viewer is shown below. + +![user-measurement-panel-modal](../../assets/img/tracking-workflow2.png) +![user-measurement-panel-modal](../../assets/img/tracking-workflow3.png) + + +#### Overview video for loading DICOM SR and making a tracked series: + + +
+ +
+ +

+ +#### Overview video for loading DICOM SR and not making a tracked series: + + +
+ +
+ +

+ +
+ +
+ +### Loading DICOM SR into an Already Tracked Series + +If you have an already tracked series and try to load a DICOM SR measurements, +you will be shown the following lock icon. This means that, you can review the +DICOM SR measurement, manipulate image and draw "temporary" measurements; however, +you cannot edit the DICOM SR measurement. + + +![locked-sr](../../assets/img/locked-sr.png) + +

+ + +#### Overview video for loading DICOM SR inside an already tracked series: + + + +
+ +
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md new file mode 100644 index 00000000000..1b5190ffaeb --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/study-panel.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 2 +--- + +# Study Panel + +In `Basic Viewer` mode, the left panel includes Studies related to the current +patient. You can see three main type of studies below + +- Primary: The opened study from the study list. This study is always expanded + by default. +- Recent: All studies for the patient that contain study dates within 1 year of + the primary study +- All: All studies available for the patient contained within the source + repository + +The `Study Panel` displays the measurement tracking status of each series within +a study. As you can see in the first picture, the dashed circle on the left side +of each series demonstrates whether the series is being tracked for measurement +or not. + + + +![user-study-panel](../../assets/img/user-study-panel.png) + +Studies can be expanded or collapsed by clicking on the study information in the +Study Panel. If a series is being tracking within a study, the Measurement Panel +will display this information while the study is collapsed. + + + + diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md new file mode 100644 index 00000000000..9f226e0d6f7 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/toolbar.md @@ -0,0 +1,84 @@ +--- +sidebar_position: 6 +--- + + +# Toolbar + +The four main components of the toolbar are: + +- Navigation back to the [Study List](../index.md) +- Logo and white labelling +- [Tools](#tools) +- [Preferences](#preferences) + +![user-viewer-toolbar](../../assets/img/user-viewer-toolbar.png) + + +## Tools +This section displays all the available tools inside the mode. +## Measurement tools +The basic viewer comes with the following default measurement tools: + +- Length Tool: Calculates the linear distance between two points in *mm* +- Bidirectional Tool: Creates a measurement of the longest diameter (LD) and longest perpendicular diameter (LPD) in *mm* +- Annotation: Used to create a qualitative marker with a freetext label +- Ellipse: Measures an elliptical area in *mm2* and Hounsfield Units (HU) + +When a measurement tool is selected from the toolbar, it becomes the `active` tool. Use the caret to expand the measurement tools and select another tool. + + +![user-viewer-toolbar-measurements](../../assets/img/user-viewer-toolbar-measurements.png) + + +## Window/Level +The `Window/Level` tool enables manipulating the window level and window width of the rendered image. Click on the tool to enable freeform adjustment, then click and drag on the viewport to freely adjust the window/level. + +Click on the caret to expand the tool and choose from predefined W/L settings for common imaging scenarios. + + +![user-toolbar-preset](../../assets/img/user-toolbar-preset.png) + + +## Pan and Zoom +With the Zoom tool selected, click and drag the cursor on an image to adjust the zoom. The magnification level is displayed in the viewport. + +With the Pan tool selected, click and drag the cursor on an image to adjust the image position. + +## Image Capture +Click on the Camera icon to download a high quality image capture using common image formats (png, jpg) + +![user-toolbar-download-icon](../../assets/img/user-toolbar-download-icon.png) + +In the opened modal, the filename, image's width and height, and filetype and can be configured before downloading the image to your local computer. + +![user-toolbarDownload](../../assets/img/user-toolbarDownload.png) + + + +## Layout Selector +Please see the `Viewport` section for details. + + +## More Tools Menu +- Reset View: Resets all image manipulation such as position, zoom, and W/L +- Rotate Right: Flips the image 90 degrees clockwise +- Flip Horizontally: Flips the image 180 degrees horizontally +- Stack Scroll: Links all viewports containing images to scroll together +- Magnify: Click on an image to magnify a particular area of interest +- Invert: Inverts the color scale +- Cine: Toggles the Cine player control in the currently selected viewport. Click the `x` on the Cine player or click the tool again to toggle off. +- Angle: Measures an adjustable angle on an image +- Probe: Drag the probe to see pixel values +- Rectangle: Measures a rectangular area in mm^2 and HU + +When a tool is selected from the `More Tools` menu, it becomes the active tool until it is replaced by clicking on a different tool in the More Tools menu or main toolbar. + + +## Overview Video +An overview of tool usage can been seen below: + + +
+ +
diff --git a/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md new file mode 100644 index 00000000000..173728ca153 --- /dev/null +++ b/platform/docs/versioned_docs/version-3.0/user-guide/viewer/viewport.md @@ -0,0 +1,39 @@ +--- +sidebar_position: 5 +--- + +# Viewport + +Image visualization happens at the viewport which contains canvas or canvases that +renders series. + +![user-viewer-main](../../assets/img/user-viewer-main.png) + + +By default, you can modify: + +- Zoom: right click dragging up or down +- Contrast/brightness: left click dragging up/down to change contrast, and left/right for changing brightness +- Pan: middle click dragging + + +## Changing Series for display +To change the displayed series, you can drag and drop the desired series from the left panel. Start, by dragging the thumbnail of the series, and drop it on the viewport. + +## Changing Layout +If you click on the layout icon on the toolbar, you can use the layout selector UI. After changing the layout, you can select studies for each new viewport by dragging and dropping in to the viewport. + +After changing the layout from 1x1, you will see each viewport gets tagged by a letter, +which matches its series section in the study list. + + +![user-viewer-layout](../../assets/img/user-viewer-layout.png) + + +## Overview Video +An overview of viewport layout change, and manipulation can be seen below: + + +
+ +
diff --git a/platform/docs/versioned_sidebars/version-3.0-sidebars.json b/platform/docs/versioned_sidebars/version-3.0-sidebars.json new file mode 100644 index 00000000000..caea0c03ba6 --- /dev/null +++ b/platform/docs/versioned_sidebars/version-3.0-sidebars.json @@ -0,0 +1,8 @@ +{ + "tutorialSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/platform/docs/versions.json b/platform/docs/versions.json index d288749db51..8858c6ab305 100644 --- a/platform/docs/versions.json +++ b/platform/docs/versions.json @@ -1,4 +1 @@ -[ - "2.0", - "1.0" -] +["2.0"] diff --git a/platform/docs/versionsArchived.json b/platform/docs/versionsArchived.json new file mode 100644 index 00000000000..01cc3485c87 --- /dev/null +++ b/platform/docs/versionsArchived.json @@ -0,0 +1,4 @@ +{ + "Version 3.0 - Cornerstone Legacy": "https://deploy-preview-2791--ohif-platform-docs.netlify.app/", + "Version 1.0": "https://deploy-preview-2791--ohif-platform-docs.netlify.app/1.0/" +} diff --git a/platform/i18n/.webpack/webpack.prod.js b/platform/i18n/.webpack/webpack.prod.js index fd164230cf6..39de350816d 100644 --- a/platform/i18n/.webpack/webpack.prod.js +++ b/platform/i18n/.webpack/webpack.prod.js @@ -12,7 +12,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/platform/ui/.webpack/webpack.prod.js b/platform/ui/.webpack/webpack.prod.js index 016f5da281a..f0f0b8d372f 100644 --- a/platform/ui/.webpack/webpack.prod.js +++ b/platform/ui/.webpack/webpack.prod.js @@ -13,7 +13,6 @@ module.exports = (env, argv) => { const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); return merge(commonConfig, { - devtool: 'source-map', stats: { colors: true, hash: true, diff --git a/platform/ui/src/assets/icons/edit-patient.svg b/platform/ui/src/assets/icons/edit-patient.svg new file mode 100644 index 00000000000..cd37992127f --- /dev/null +++ b/platform/ui/src/assets/icons/edit-patient.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/platform/ui/src/assets/icons/tool-create-threshold.svg b/platform/ui/src/assets/icons/tool-create-threshold.svg new file mode 100644 index 00000000000..2385f807393 --- /dev/null +++ b/platform/ui/src/assets/icons/tool-create-threshold.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/platform/ui/src/assets/icons/tool-crosshair.svg b/platform/ui/src/assets/icons/tool-crosshair.svg new file mode 100644 index 00000000000..a03906e79ff --- /dev/null +++ b/platform/ui/src/assets/icons/tool-crosshair.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/platform/ui/src/assets/icons/tool-fusion-color.svg b/platform/ui/src/assets/icons/tool-fusion-color.svg new file mode 100644 index 00000000000..a09783d8c11 --- /dev/null +++ b/platform/ui/src/assets/icons/tool-fusion-color.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/platform/ui/src/components/AboutModal/AboutModal.jsx b/platform/ui/src/components/AboutModal/AboutModal.tsx similarity index 100% rename from platform/ui/src/components/AboutModal/AboutModal.jsx rename to platform/ui/src/components/AboutModal/AboutModal.tsx diff --git a/platform/ui/src/components/Button/Button.jsx b/platform/ui/src/components/Button/Button.tsx similarity index 98% rename from platform/ui/src/components/Button/Button.jsx rename to platform/ui/src/components/Button/Button.tsx index bbce4139185..b32b8d2c703 100644 --- a/platform/ui/src/components/Button/Button.jsx +++ b/platform/ui/src/components/Button/Button.tsx @@ -166,9 +166,7 @@ Button.propTypes = { size: PropTypes.oneOf(['small', 'medium', 'large', 'initial', 'inherit']), /** Button corner roundness */ rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']), - /** Button variants */ - variant: PropTypes.oneOf(['text', 'outlined', 'contained']), - /** Button color */ + variant: PropTypes.oneOf(['text', 'outlined', 'contained', 'disabled']), color: PropTypes.oneOf([ 'default', 'primary', diff --git a/platform/ui/src/components/ButtonGroup/ButtonGroup.jsx b/platform/ui/src/components/ButtonGroup/ButtonGroup.tsx similarity index 100% rename from platform/ui/src/components/ButtonGroup/ButtonGroup.jsx rename to platform/ui/src/components/ButtonGroup/ButtonGroup.tsx diff --git a/platform/ui/src/components/CinePlayer/CinePlayer.jsx b/platform/ui/src/components/CinePlayer/CinePlayer.tsx similarity index 100% rename from platform/ui/src/components/CinePlayer/CinePlayer.jsx rename to platform/ui/src/components/CinePlayer/CinePlayer.tsx diff --git a/platform/ui/src/components/ContextMenu/ContextMenu.jsx b/platform/ui/src/components/ContextMenu/ContextMenu.tsx similarity index 100% rename from platform/ui/src/components/ContextMenu/ContextMenu.jsx rename to platform/ui/src/components/ContextMenu/ContextMenu.tsx diff --git a/platform/ui/src/components/ContextMenuMeasurements/ContextMenuMeasurements.jsx b/platform/ui/src/components/ContextMenuMeasurements/ContextMenuMeasurements.tsx similarity index 100% rename from platform/ui/src/components/ContextMenuMeasurements/ContextMenuMeasurements.jsx rename to platform/ui/src/components/ContextMenuMeasurements/ContextMenuMeasurements.tsx diff --git a/platform/ui/src/components/DateRange/DateRange.jsx b/platform/ui/src/components/DateRange/DateRange.tsx similarity index 100% rename from platform/ui/src/components/DateRange/DateRange.jsx rename to platform/ui/src/components/DateRange/DateRange.tsx diff --git a/platform/ui/src/components/Dialog/Body.js b/platform/ui/src/components/Dialog/Body.tsx similarity index 100% rename from platform/ui/src/components/Dialog/Body.js rename to platform/ui/src/components/Dialog/Body.tsx diff --git a/platform/ui/src/components/Dialog/Dialog.jsx b/platform/ui/src/components/Dialog/Dialog.tsx similarity index 100% rename from platform/ui/src/components/Dialog/Dialog.jsx rename to platform/ui/src/components/Dialog/Dialog.tsx diff --git a/platform/ui/src/components/Dialog/Footer.js b/platform/ui/src/components/Dialog/Footer.tsx similarity index 100% rename from platform/ui/src/components/Dialog/Footer.js rename to platform/ui/src/components/Dialog/Footer.tsx diff --git a/platform/ui/src/components/Dialog/Header.js b/platform/ui/src/components/Dialog/Header.tsx similarity index 100% rename from platform/ui/src/components/Dialog/Header.js rename to platform/ui/src/components/Dialog/Header.tsx diff --git a/platform/ui/src/components/Dropdown/Dropdown.jsx b/platform/ui/src/components/Dropdown/Dropdown.tsx similarity index 100% rename from platform/ui/src/components/Dropdown/Dropdown.jsx rename to platform/ui/src/components/Dropdown/Dropdown.tsx diff --git a/platform/ui/src/components/EmptyStudies/EmptyStudies.js b/platform/ui/src/components/EmptyStudies/EmptyStudies.tsx similarity index 100% rename from platform/ui/src/components/EmptyStudies/EmptyStudies.js rename to platform/ui/src/components/EmptyStudies/EmptyStudies.tsx diff --git a/platform/ui/src/components/ErrorBoundary/ErrorBoundary.jsx b/platform/ui/src/components/ErrorBoundary/ErrorBoundary.tsx similarity index 100% rename from platform/ui/src/components/ErrorBoundary/ErrorBoundary.jsx rename to platform/ui/src/components/ErrorBoundary/ErrorBoundary.tsx diff --git a/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.tsx similarity index 100% rename from platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx rename to platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.tsx diff --git a/platform/ui/src/components/Header/Header.jsx b/platform/ui/src/components/Header/Header.tsx similarity index 100% rename from platform/ui/src/components/Header/Header.jsx rename to platform/ui/src/components/Header/Header.tsx diff --git a/platform/ui/src/components/HotkeyField/HotkeyField.jsx b/platform/ui/src/components/HotkeyField/HotkeyField.tsx similarity index 100% rename from platform/ui/src/components/HotkeyField/HotkeyField.jsx rename to platform/ui/src/components/HotkeyField/HotkeyField.tsx diff --git a/platform/ui/src/components/HotkeyField/index.js b/platform/ui/src/components/HotkeyField/index.js index 890117cf675..f56296bf69b 100644 --- a/platform/ui/src/components/HotkeyField/index.js +++ b/platform/ui/src/components/HotkeyField/index.js @@ -1,3 +1,3 @@ -import HotkeyField from './HotkeyField.jsx'; +import HotkeyField from './HotkeyField.tsx'; export default HotkeyField; diff --git a/platform/ui/src/components/HotkeysPreferences/HotkeysPreferences.jsx b/platform/ui/src/components/HotkeysPreferences/HotkeysPreferences.tsx similarity index 100% rename from platform/ui/src/components/HotkeysPreferences/HotkeysPreferences.jsx rename to platform/ui/src/components/HotkeysPreferences/HotkeysPreferences.tsx diff --git a/platform/ui/src/components/HotkeysPreferences/index.js b/platform/ui/src/components/HotkeysPreferences/index.js index d3f9621c718..2a641ec06ba 100644 --- a/platform/ui/src/components/HotkeysPreferences/index.js +++ b/platform/ui/src/components/HotkeysPreferences/index.js @@ -1,3 +1,3 @@ -import HotkeysPreferences from './HotkeysPreferences.jsx'; +import HotkeysPreferences from './HotkeysPreferences.tsx'; export default HotkeysPreferences; diff --git a/platform/ui/src/components/Icon/Icon.jsx b/platform/ui/src/components/Icon/Icon.tsx similarity index 100% rename from platform/ui/src/components/Icon/Icon.jsx rename to platform/ui/src/components/Icon/Icon.tsx diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js index 2f49e40f733..c9af36fb694 100644 --- a/platform/ui/src/components/Icon/getIcon.js +++ b/platform/ui/src/components/Icon/getIcon.js @@ -55,10 +55,14 @@ import toolFlipHorizontal from './../../assets/icons/tool-flip-horizontal.svg'; import toolInvert from './../../assets/icons/tool-invert.svg'; import toolRotateRight from './../../assets/icons/tool-rotate-right.svg'; import toolCine from './../../assets/icons/tool-cine.svg'; +import toolCrosshair from './../../assets/icons/tool-crosshair.svg'; import toolProbe from './../../assets/icons/tool-probe.svg'; import toolAngle from './../../assets/icons/tool-angle.svg'; import toolReset from './../../assets/icons/tool-reset.svg'; import toolRectangle from './../../assets/icons/tool-rectangle.svg'; +import toolFusionColor from './../../assets/icons/tool-fusion-color.svg'; +import toolCreateThreshold from './../../assets/icons/tool-create-threshold.svg'; +import editPatient from './../../assets/icons/edit-patient.svg'; /** Old OHIF */ import oldTrash from './../../assets/icons/old-trash.svg'; @@ -120,10 +124,14 @@ const ICONS = { 'tool-invert': toolInvert, 'tool-rotate-right': toolRotateRight, 'tool-cine': toolCine, + 'tool-crosshair': toolCrosshair, 'tool-probe': toolProbe, 'tool-angle': toolAngle, 'tool-reset': toolReset, 'tool-rectangle': toolRectangle, + 'tool-fusion-color': toolFusionColor, + 'tool-create-threshold': toolCreateThreshold, + 'edit-patient': editPatient, /** Old OHIF */ 'old-trash': oldTrash, diff --git a/platform/ui/src/components/IconButton/IconButton.jsx b/platform/ui/src/components/IconButton/IconButton.tsx similarity index 100% rename from platform/ui/src/components/IconButton/IconButton.jsx rename to platform/ui/src/components/IconButton/IconButton.tsx diff --git a/platform/ui/src/components/ImageScrollbar/ImageScrollbar.css b/platform/ui/src/components/ImageScrollbar/ImageScrollbar.css new file mode 100644 index 00000000000..6d1aaf32d05 --- /dev/null +++ b/platform/ui/src/components/ImageScrollbar/ImageScrollbar.css @@ -0,0 +1,106 @@ +.scroll { + height: 100%; + padding: 5px; + position: absolute; + right: 0; + top: 0; +} +.scroll .scroll-holder { + height: calc(100% - 20px); + margin-top: 5px; + position: relative; + width: 12px; +} +.scroll .scroll-holder .imageSlider { + height: 12px; + left: 12px; + padding: 0; + position: absolute; + top: 0; + transform: rotate(90deg); + transform-origin: top left; + -webkit-appearance: none; + background-color: rgba(0, 0, 0, 0); +} +.scroll .scroll-holder .imageSlider:focus { + outline: none; +} +.scroll .scroll-holder .imageSlider::-moz-focus-outer { + border: none; +} +.scroll .scroll-holder .imageSlider::-webkit-slider-runnable-track { + background-color: rgba(0, 0, 0, 0); + border: none; + cursor: pointer; + height: 5px; + z-index: 6; +} +.scroll .scroll-holder .imageSlider::-moz-range-track { + background-color: rgba(0, 0, 0, 0); + border: none; + cursor: pointer; + height: 2px; + z-index: 6; +} +.scroll .scroll-holder .imageSlider::-ms-track { + animate: 0.2s; + background: transparent; + border: none; + border-width: 15px 0; + color: rgba(0, 0, 0, 0); + cursor: pointer; + height: 12px; + width: 100%; +} +.scroll .scroll-holder .imageSlider::-ms-fill-lower { + background: rgba(0, 0, 0, 0); +} +.scroll .scroll-holder .imageSlider::-ms-fill-upper { + background: rgba(0, 0, 0, 0); +} +.scroll .scroll-holder .imageSlider::-webkit-slider-thumb { + -webkit-appearance: none !important; + background-color: #163239; + border: none; + border-radius: 57px; + cursor: -webkit-grab; + height: 12px; + margin-top: -4px; + width: 39px; +} +.scroll .scroll-holder .imageSlider::-webkit-slider-thumb:active { + background-color: #20a5d6; + cursor: -webkit-grabbing; +} +.scroll .scroll-holder .imageSlider::-moz-range-thumb { + background-color: #163239; + border: none; + border-radius: 57px; + cursor: -moz-grab; + height: 12px; + width: 39px; + z-index: 7; +} +.scroll .scroll-holder .imageSlider::-moz-range-thumb:active { + background-color: #20a5d6; + cursor: -moz-grabbing; +} +.scroll .scroll-holder .imageSlider::-ms-thumb { + background-color: #163239; + border: none; + border-radius: 57px; + cursor: ns-resize; + height: 12px; + width: 39px; +} +.scroll .scroll-holder .imageSlider::-ms-thumb:active { + background-color: #20a5d6; +} +.scroll .scroll-holder .imageSlider::-ms-tooltip { + display: none; +} +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .imageSlider { + left: 50px; + } +} diff --git a/platform/ui/src/components/ImageScrollbar/ImageScrollbar.tsx b/platform/ui/src/components/ImageScrollbar/ImageScrollbar.tsx new file mode 100644 index 00000000000..f7cb7d8e91c --- /dev/null +++ b/platform/ui/src/components/ImageScrollbar/ImageScrollbar.tsx @@ -0,0 +1,67 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import './ImageScrollbar.css'; + +class ImageScrollbar extends PureComponent { + static propTypes = { + value: PropTypes.number.isRequired, + max: PropTypes.number.isRequired, + height: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + }; + + render() { + if (this.props.max === 0) { + return null; + } + + this.style = { + width: `${this.props.height}`, + }; + + return ( +
+
+ +
+
+ ); + } + + onChange = event => { + const intValue = parseInt(event.target.value, 10); + this.props.onChange(intValue); + }; + + onKeyDown = event => { + // We don't allow direct keyboard up/down input on the + // image sliders since the natural direction is reversed (0 is at the top) + + // Store the KeyCodes in an object for readability + const keys = { + DOWN: 40, + UP: 38, + }; + + // TODO: Enable scroll down / scroll up without depending on ohif-core + if (event.which === keys.DOWN) { + //OHIF.commands.run('scrollDown'); + event.preventDefault(); + } else if (event.which === keys.UP) { + //OHIF.commands.run('scrollUp'); + event.preventDefault(); + } + }; +} + +export default ImageScrollbar; diff --git a/platform/ui/src/components/ImageScrollbar/index.js b/platform/ui/src/components/ImageScrollbar/index.js new file mode 100644 index 00000000000..026e347ef3f --- /dev/null +++ b/platform/ui/src/components/ImageScrollbar/index.js @@ -0,0 +1,2 @@ +import ImageScrollbar from './ImageScrollbar'; +export default ImageScrollbar; diff --git a/platform/ui/src/components/Input/Input.jsx b/platform/ui/src/components/Input/Input.tsx similarity index 100% rename from platform/ui/src/components/Input/Input.jsx rename to platform/ui/src/components/Input/Input.tsx diff --git a/platform/ui/src/components/InputDateRange/InputDateRange.jsx b/platform/ui/src/components/InputDateRange/InputDateRange.tsx similarity index 100% rename from platform/ui/src/components/InputDateRange/InputDateRange.jsx rename to platform/ui/src/components/InputDateRange/InputDateRange.tsx diff --git a/platform/ui/src/components/InputGroup/InputGroup.jsx b/platform/ui/src/components/InputGroup/InputGroup.tsx similarity index 100% rename from platform/ui/src/components/InputGroup/InputGroup.jsx rename to platform/ui/src/components/InputGroup/InputGroup.tsx diff --git a/platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.jsx b/platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.tsx similarity index 100% rename from platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.jsx rename to platform/ui/src/components/InputLabelWrapper/InputLabelWrapper.tsx diff --git a/platform/ui/src/components/InputMultiSelect/InputMultiSelect.jsx b/platform/ui/src/components/InputMultiSelect/InputMultiSelect.tsx similarity index 100% rename from platform/ui/src/components/InputMultiSelect/InputMultiSelect.jsx rename to platform/ui/src/components/InputMultiSelect/InputMultiSelect.tsx diff --git a/platform/ui/src/components/InputText/InputText.jsx b/platform/ui/src/components/InputText/InputText.tsx similarity index 100% rename from platform/ui/src/components/InputText/InputText.jsx rename to platform/ui/src/components/InputText/InputText.tsx diff --git a/platform/ui/src/components/Label/Label.jsx b/platform/ui/src/components/Label/Label.tsx similarity index 100% rename from platform/ui/src/components/Label/Label.jsx rename to platform/ui/src/components/Label/Label.tsx diff --git a/platform/ui/src/components/LayoutSelector/LayoutSelector.jsx b/platform/ui/src/components/LayoutSelector/LayoutSelector.tsx similarity index 100% rename from platform/ui/src/components/LayoutSelector/LayoutSelector.jsx rename to platform/ui/src/components/LayoutSelector/LayoutSelector.tsx diff --git a/platform/ui/src/components/ListMenu/ListMenu.jsx b/platform/ui/src/components/ListMenu/ListMenu.tsx similarity index 100% rename from platform/ui/src/components/ListMenu/ListMenu.jsx rename to platform/ui/src/components/ListMenu/ListMenu.tsx diff --git a/platform/ui/src/components/MeasurementTable/MeasurementItem.js b/platform/ui/src/components/MeasurementTable/MeasurementItem.tsx similarity index 93% rename from platform/ui/src/components/MeasurementTable/MeasurementItem.js rename to platform/ui/src/components/MeasurementTable/MeasurementItem.tsx index 45d63589b4f..045235cb172 100644 --- a/platform/ui/src/components/MeasurementTable/MeasurementItem.js +++ b/platform/ui/src/components/MeasurementTable/MeasurementItem.tsx @@ -4,7 +4,7 @@ import classnames from 'classnames'; import { Icon } from '../'; const MeasurementItem = ({ - id, + uid, index, label, displayText, @@ -16,10 +16,10 @@ const MeasurementItem = ({ const onEditHandler = event => { event.stopPropagation(); - onEdit({ id, isActive, event }); + onEdit({ uid, isActive, event }); }; - const onClickHandler = event => onClick({ id, isActive, event }); + const onClickHandler = event => onClick({ uid, isActive, event }); const onMouseEnter = () => setIsHovering(true); const onMouseLeave = () => setIsHovering(false); @@ -37,7 +37,7 @@ const MeasurementItem = ({ onClick={onClickHandler} role="button" tabIndex="0" - data-cy={"measurement-item"} + data-cy={'measurement-item'} >
{ - const { t } = useTranslation("MeasurementTable") + const { t } = useTranslation('MeasurementTable'); return (
@@ -19,8 +19,8 @@ const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => { {data.length !== 0 && data.map((measurementItem, index) => ( { + const [isHovering, setIsHovering] = useState(false); + + const onEditHandler = event => { + event.stopPropagation(); + onEdit({ id, isActive, event }); + }; + + const onClickHandler = event => onClick({ id, isActive, event }); + + const onMouseEnter = () => setIsHovering(true); + const onMouseLeave = () => setIsHovering(false); + + return ( +
+
+ {isHovering ? ( + { + e.stopPropagation(); + onDelete(id); + }} + /> + ) : ( + {index} + )} +
+
+
+
+
+ {label} +
+
+
+ onEditHandler(e)} + /> +
+
+ { + // stopPropagation needed to avoid disable the current active item + e.stopPropagation(); + toggleVisibility(e, id); + }} + /> +
+
+
+
+ {displayText && + displayText.map(line => ( + + {line} + + ))} +
+
+
+ ); +}; + +SegmentationItem.propTypes = { + id: PropTypes.oneOfType([ + PropTypes.number.isRequired, + PropTypes.string.isRequired, + ]), + index: PropTypes.number.isRequired, + label: PropTypes.string, + displayText: PropTypes.arrayOf(PropTypes.string), + isActive: PropTypes.bool, + isVisible: PropTypes.bool, + onClick: PropTypes.func, + onEdit: PropTypes.func, + onDelete: PropTypes.func, + toggleVisibility: PropTypes.func, +}; + +SegmentationItem.defaultProps = { + isActive: false, + displayText: [], +}; + +export default SegmentationItem; diff --git a/platform/ui/src/components/SegmentationTable/SegmentationTable.jsx b/platform/ui/src/components/SegmentationTable/SegmentationTable.jsx deleted file mode 100644 index 3b7cb1b1946..00000000000 --- a/platform/ui/src/components/SegmentationTable/SegmentationTable.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; -import { Icon } from '../'; - -const SegmentationTable = ({ title, amount, data }) => { - const [activeItem, setActiveItem] = useState(null); - - // TODO: colorLUT should be defined in the application - const colorLUT = [ - '#932a13', - '#821393', - '#2b7fc4', - '#12c457', - '#7f7d2e', - '#7f2e4a', - '#866943', - '#65ae7f', - '#334b5f', - '#3cb0dc', - '#292ac9', - '#629753', - ]; - - return ( -
-
- - {title} - -
- {amount} - alert('TBD')} - /> -
-
-
- {!!data.length && - data.map((e, i) => { - const itemKey = i; - const currentItem = i + 1; - const isActive = !!activeItem && activeItem[title] === i; - - const handleOnClick = () => { - setActiveItem((s) => { - return { - ...s, - [title]: s && s[title] === itemKey ? null : itemKey, - }; - }); - }; - - return ( -
-
- {currentItem} -
-
- -
- Label short description -
- { - // stopPropagation needed to avoid disable the current active item - e.stopPropagation(); - alert('Toggle'); - }} - /> -
-
- ); - })} -
-
- ); -}; - -SegmentationTable.defaultProps = { - amount: null, - data: [], -}; - -SegmentationTable.propTypes = { - title: PropTypes.string.isRequired, - amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - data: PropTypes.array, // TODO: define better the array structure -}; - -export default SegmentationTable; diff --git a/platform/ui/src/components/SegmentationTable/SegmentationTable.tsx b/platform/ui/src/components/SegmentationTable/SegmentationTable.tsx new file mode 100644 index 00000000000..8b5559dd835 --- /dev/null +++ b/platform/ui/src/components/SegmentationTable/SegmentationTable.tsx @@ -0,0 +1,130 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { Icon } from '../'; +import SegmentationItem from './SegmentationItem'; + +const SegmentationTable = ({ + title, + amount, + segmentations, + activeSegmentationId, + onClick, + onEdit, + onDelete, + onToggleVisibility, + onToggleVisibilityAll, +}) => { + const [hiddenSegmentationIds, setHiddenSegmentationIds] = useState([]); + + const handleToggleAll = () => { + // filter segmentation ids that are hidden + const visibleSegmentationsIds = segmentations + .filter(segmentation => !hiddenSegmentationIds.includes(segmentation.id)) + .map(segmentation => segmentation.id); + + // check if there is at least one visible segmentation + // if there is set all to invisible state + if (visibleSegmentationsIds.length > 0) { + // make all invisible + setHiddenSegmentationIds( + segmentations.map(segmentation => segmentation.id) + ); + + // toggle those that are visible + onToggleVisibilityAll(visibleSegmentationsIds); + } + + // if there is no visible segmentation, set + // all to visible state + if (visibleSegmentationsIds.length === 0) { + // copy hidden segmentation ids + const Ids = [...hiddenSegmentationIds]; + setHiddenSegmentationIds([]); + + // toggle those that are hidden + onToggleVisibilityAll(Ids); + } + }; + + return ( +
+
+ + {title} + +
+ {amount} + handleToggleAll()} + /> +
+
+
+ {!!segmentations.length && + segmentations.map((segmentation, i) => { + const { id, label, displayText = [] } = segmentation; + return ( + { + onClick(id); + }} + onEdit={() => { + onEdit(id); + }} + onDelete={() => { + onDelete(id); + }} + toggleVisibility={() => { + onToggleVisibility(id); + + // if segmentation is visible, remove it from hiddenSegmentationIds + if (hiddenSegmentationIds.includes(id)) { + setHiddenSegmentationIds( + hiddenSegmentationIds.filter(hiddenId => hiddenId !== id) + ); + } else { + // if segmentation is hidden, add it to hiddenSegmentationIds + setHiddenSegmentationIds([...hiddenSegmentationIds, id]); + } + }} + /> + ); + })} +
+
+ ); +}; + +SegmentationTable.propTypes = { + title: PropTypes.string.isRequired, + amount: PropTypes.number.isRequired, + segmentations: PropTypes.array.isRequired, + activeSegmentationId: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + onEdit: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onToggleVisibility: PropTypes.func.isRequired, + onToggleVisibilityAll: PropTypes.func.isRequired, +}; + +SegmentationTable.defaultProps = { + title: '', + amount: 0, + segmentations: [], + activeSegmentationId: '', + onClick: () => {}, + onEdit: () => {}, + onToggleVisibility: () => {}, + onToggleVisibilityAll: () => {}, +}; + +export default SegmentationTable; diff --git a/platform/ui/src/components/Select/Select.jsx b/platform/ui/src/components/Select/Select.tsx similarity index 91% rename from platform/ui/src/components/Select/Select.jsx rename to platform/ui/src/components/Select/Select.tsx index 9d96f8f2e2b..be13a866c72 100644 --- a/platform/ui/src/components/Select/Select.jsx +++ b/platform/ui/src/components/Select/Select.tsx @@ -48,9 +48,14 @@ const Select = ({ menuPlacement, value, }) => { - const _noIconComponents = { DropdownIndicator: () => null, IndicatorSeparator: () => null }; + const _noIconComponents = { + DropdownIndicator: () => null, + IndicatorSeparator: () => null, + }; let _components = isMulti ? { Option, MultiValue } : {}; - _components = noIcons ? { ..._components, ..._noIconComponents } : _components; + _components = noIcons + ? { ..._components, ..._noIconComponents } + : _components; const selectedOptions = []; // Map array of values to an array of selected options @@ -123,7 +128,10 @@ Select.propTypes = { }) ), placeholder: PropTypes.string, - value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.any]), + value: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.any, + ]), }; export default Select; diff --git a/platform/ui/src/components/SidePanel/SidePanel.jsx b/platform/ui/src/components/SidePanel/SidePanel.tsx similarity index 100% rename from platform/ui/src/components/SidePanel/SidePanel.jsx rename to platform/ui/src/components/SidePanel/SidePanel.tsx diff --git a/platform/ui/src/components/Snackbar/SnackbarContainer.jsx b/platform/ui/src/components/Snackbar/SnackbarContainer.tsx similarity index 100% rename from platform/ui/src/components/Snackbar/SnackbarContainer.jsx rename to platform/ui/src/components/Snackbar/SnackbarContainer.tsx diff --git a/platform/ui/src/components/Snackbar/SnackbarItem.jsx b/platform/ui/src/components/Snackbar/SnackbarItem.tsx similarity index 100% rename from platform/ui/src/components/Snackbar/SnackbarItem.jsx rename to platform/ui/src/components/Snackbar/SnackbarItem.tsx diff --git a/platform/ui/src/components/SplitButton/SplitButton.jsx b/platform/ui/src/components/SplitButton/SplitButton.tsx similarity index 96% rename from platform/ui/src/components/SplitButton/SplitButton.jsx rename to platform/ui/src/components/SplitButton/SplitButton.tsx index a57f4983e56..e0df0ce174f 100644 --- a/platform/ui/src/components/SplitButton/SplitButton.jsx +++ b/platform/ui/src/components/SplitButton/SplitButton.tsx @@ -100,8 +100,7 @@ const SplitButton = ({ interactionType: item.type, // splitButtonId? (so we can track group?) // info to fire item's command/event? - commandName: item.commandName, - commandOptions: item.commandOptions, + commands: item.commands, }); setState(state => ({ @@ -140,8 +139,7 @@ const SplitButton = ({ // splitButtonId? (so we can track group?) // info to fire item's command/event? // - commandName: state.primary.commandName, - commandOptions: state.primary.commandOptions, + commands: state.primary.commands, }); }; @@ -224,7 +222,10 @@ const SplitButton = ({ className={classes.Content({ ...state })} data-cy={`${groupId}-list-menu`} > - renderer({ ...args, t })} /> + renderer({ ...args, t })} + />
@@ -292,7 +293,7 @@ SplitButton.propTypes = { isActive: PropTypes.bool, }) ), - /** Callback function to inform ToolbarService of important events */ + /** Callback function to inform ToolBarService of important events */ onInteraction: PropTypes.func.isRequired, }; diff --git a/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx b/platform/ui/src/components/StudyBrowser/StudyBrowser.tsx similarity index 91% rename from platform/ui/src/components/StudyBrowser/StudyBrowser.jsx rename to platform/ui/src/components/StudyBrowser/StudyBrowser.tsx index 32c1007155e..d75313b7682 100644 --- a/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx +++ b/platform/ui/src/components/StudyBrowser/StudyBrowser.tsx @@ -28,13 +28,12 @@ const StudyBrowser = ({ onClickThumbnail, onDoubleClickThumbnail, onClickUntrack, - activeDisplaySetInstanceUID, + activeDisplaySetInstanceUIDs, }) => { - const { t } = useTranslation("StudyBrowser") + const { t } = useTranslation('StudyBrowser'); const getTabContent = () => { const tabData = tabs.find(tab => tab.name === activeTabName); - return tabData.studies.map( ({ studyInstanceUid, @@ -62,7 +61,7 @@ const StudyBrowser = ({ {isExpanded && displaySets && ( -
+
{ }; +const noop = () => {}; StudyBrowser.defaultProps = { onClickTab: noop, diff --git a/platform/ui/src/components/StudyItem/StudyItem.jsx b/platform/ui/src/components/StudyItem/StudyItem.tsx similarity index 100% rename from platform/ui/src/components/StudyItem/StudyItem.jsx rename to platform/ui/src/components/StudyItem/StudyItem.tsx diff --git a/platform/ui/src/components/StudyListExpandedRow/StudyListExpandedRow.js b/platform/ui/src/components/StudyListExpandedRow/StudyListExpandedRow.tsx similarity index 100% rename from platform/ui/src/components/StudyListExpandedRow/StudyListExpandedRow.js rename to platform/ui/src/components/StudyListExpandedRow/StudyListExpandedRow.tsx diff --git a/platform/ui/src/components/StudyListFilter/StudyListFilter.jsx b/platform/ui/src/components/StudyListFilter/StudyListFilter.tsx similarity index 100% rename from platform/ui/src/components/StudyListFilter/StudyListFilter.jsx rename to platform/ui/src/components/StudyListFilter/StudyListFilter.tsx diff --git a/platform/ui/src/components/StudyListPagination/StudyListPagination.js b/platform/ui/src/components/StudyListPagination/StudyListPagination.tsx similarity index 100% rename from platform/ui/src/components/StudyListPagination/StudyListPagination.js rename to platform/ui/src/components/StudyListPagination/StudyListPagination.tsx diff --git a/platform/ui/src/components/StudyListTable/StudyListTable.js b/platform/ui/src/components/StudyListTable/StudyListTable.tsx similarity index 100% rename from platform/ui/src/components/StudyListTable/StudyListTable.js rename to platform/ui/src/components/StudyListTable/StudyListTable.tsx diff --git a/platform/ui/src/components/StudyListTable/StudyListTableRow.js b/platform/ui/src/components/StudyListTable/StudyListTableRow.tsx similarity index 100% rename from platform/ui/src/components/StudyListTable/StudyListTableRow.js rename to platform/ui/src/components/StudyListTable/StudyListTableRow.tsx diff --git a/platform/ui/src/components/StudyListTable/index.js b/platform/ui/src/components/StudyListTable/index.js index c4a0f805bca..5038404dfeb 100644 --- a/platform/ui/src/components/StudyListTable/index.js +++ b/platform/ui/src/components/StudyListTable/index.js @@ -1,5 +1,5 @@ -import StudyListTable from './StudyListTable.js'; -import StudyListTableRow from './StudyListTableRow.js'; +import StudyListTable from './StudyListTable.tsx'; +import StudyListTableRow from './StudyListTableRow.tsx'; export default StudyListTable; diff --git a/platform/ui/src/components/StudySummary/StudySummary.jsx b/platform/ui/src/components/StudySummary/StudySummary.tsx similarity index 100% rename from platform/ui/src/components/StudySummary/StudySummary.jsx rename to platform/ui/src/components/StudySummary/StudySummary.tsx diff --git a/platform/ui/src/components/StudySummary/index.js b/platform/ui/src/components/StudySummary/index.js index e9d71324f2a..e3ec616c7c5 100644 --- a/platform/ui/src/components/StudySummary/index.js +++ b/platform/ui/src/components/StudySummary/index.js @@ -1,3 +1,3 @@ -import StudySummary from './StudySummary.jsx'; +import StudySummary from './StudySummary.tsx'; export default StudySummary; diff --git a/platform/ui/src/components/Svg/Svg.jsx b/platform/ui/src/components/Svg/Svg.tsx similarity index 100% rename from platform/ui/src/components/Svg/Svg.jsx rename to platform/ui/src/components/Svg/Svg.tsx diff --git a/platform/ui/src/components/Svg/getSvg.jsx b/platform/ui/src/components/Svg/getSvg.tsx similarity index 100% rename from platform/ui/src/components/Svg/getSvg.jsx rename to platform/ui/src/components/Svg/getSvg.tsx diff --git a/platform/ui/src/components/Table/Table.jsx b/platform/ui/src/components/Table/Table.tsx similarity index 100% rename from platform/ui/src/components/Table/Table.jsx rename to platform/ui/src/components/Table/Table.tsx diff --git a/platform/ui/src/components/TableBody/TableBody.jsx b/platform/ui/src/components/TableBody/TableBody.tsx similarity index 100% rename from platform/ui/src/components/TableBody/TableBody.jsx rename to platform/ui/src/components/TableBody/TableBody.tsx diff --git a/platform/ui/src/components/TableCell/TableCell.jsx b/platform/ui/src/components/TableCell/TableCell.tsx similarity index 100% rename from platform/ui/src/components/TableCell/TableCell.jsx rename to platform/ui/src/components/TableCell/TableCell.tsx diff --git a/platform/ui/src/components/TableHead/TableHead.jsx b/platform/ui/src/components/TableHead/TableHead.tsx similarity index 100% rename from platform/ui/src/components/TableHead/TableHead.jsx rename to platform/ui/src/components/TableHead/TableHead.tsx diff --git a/platform/ui/src/components/TableRow/TableRow.jsx b/platform/ui/src/components/TableRow/TableRow.tsx similarity index 100% rename from platform/ui/src/components/TableRow/TableRow.jsx rename to platform/ui/src/components/TableRow/TableRow.tsx diff --git a/platform/ui/src/components/ThemeWrapper/ThemeWrapper.js b/platform/ui/src/components/ThemeWrapper/ThemeWrapper.tsx similarity index 100% rename from platform/ui/src/components/ThemeWrapper/ThemeWrapper.js rename to platform/ui/src/components/ThemeWrapper/ThemeWrapper.tsx diff --git a/platform/ui/src/components/Thumbnail/Thumbnail.jsx b/platform/ui/src/components/Thumbnail/Thumbnail.tsx similarity index 95% rename from platform/ui/src/components/Thumbnail/Thumbnail.jsx rename to platform/ui/src/components/Thumbnail/Thumbnail.tsx index 347c75a0561..db0743cf2ef 100644 --- a/platform/ui/src/components/Thumbnail/Thumbnail.jsx +++ b/platform/ui/src/components/Thumbnail/Thumbnail.tsx @@ -24,7 +24,7 @@ const Thumbnail = ({ // this will still allow for "drag", even if there is no drop target for the // specified item. const [collectedProps, drag, dragPreview] = useDrag({ - type: "displayset", + type: 'displayset', item: { ...dragData }, canDrag: function(monitor) { return Object.keys(dragData).length !== 0; @@ -66,7 +66,7 @@ const Thumbnail = ({
{imageAltText}
)}
-
+
{'S: '} {seriesNumber} @@ -98,7 +98,7 @@ Thumbnail.propTypes = { }), imageAltText: PropTypes.string, description: PropTypes.string.isRequired, - seriesNumber: PropTypes.number.isRequired, + seriesNumber: PropTypes.string.isRequired, numInstances: PropTypes.number.isRequired, isActive: PropTypes.bool.isRequired, onClick: PropTypes.func.isRequired, diff --git a/platform/ui/src/components/ThumbnailList/ThumbnailList.jsx b/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx similarity index 95% rename from platform/ui/src/components/ThumbnailList/ThumbnailList.jsx rename to platform/ui/src/components/ThumbnailList/ThumbnailList.tsx index eadc6a626c2..75cb700138f 100644 --- a/platform/ui/src/components/ThumbnailList/ThumbnailList.jsx +++ b/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx @@ -5,10 +5,10 @@ import { Thumbnail, ThumbnailNoImage, ThumbnailTracked } from '../'; const ThumbnailList = ({ thumbnails, - activeDisplaySetInstanceUID, onThumbnailClick, onThumbnailDoubleClick, onClickUntrack, + activeDisplaySetInstanceUIDs = [], }) => { return (
@@ -29,8 +29,9 @@ const ThumbnailList = ({ imageSrc, imageAltText, }) => { - const isActive = - activeDisplaySetInstanceUID === displaySetInstanceUID; + const isActive = activeDisplaySetInstanceUIDs.includes( + displaySetInstanceUID + ); switch (componentType) { case 'thumbnail': @@ -109,7 +110,7 @@ ThumbnailList.propTypes = { imageSrc: PropTypes.string, imageAltText: PropTypes.string, seriesDate: PropTypes.string, - seriesNumber: PropTypes.number, + seriesNumber: PropTypes.string, numInstances: PropTypes.number, description: PropTypes.string, componentType: PropTypes.oneOf([ @@ -135,7 +136,7 @@ ThumbnailList.propTypes = { }), }) ), - activeDisplaySetInstanceUID: PropTypes.string, + activeDisplaySetInstanceUIDs: PropTypes.arrayOf(PropTypes.string), onThumbnailClick: PropTypes.func.isRequired, onThumbnailDoubleClick: PropTypes.func.isRequired, onClickUntrack: PropTypes.func.isRequired, diff --git a/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.jsx b/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx similarity index 100% rename from platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.jsx rename to platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx diff --git a/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.jsx b/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.tsx similarity index 99% rename from platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.jsx rename to platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.tsx index 9a056c3e29c..fd9d7401492 100644 --- a/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.jsx +++ b/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.tsx @@ -89,7 +89,6 @@ const ThumbnailTracked = ({ )}
-
} > @@ -138,7 +137,7 @@ ThumbnailTracked.propTypes = { imageSrc: PropTypes.string, imageAltText: PropTypes.string, description: PropTypes.string.isRequired, - seriesNumber: PropTypes.number.isRequired, + seriesNumber: PropTypes.string.isRequired, numInstances: PropTypes.number.isRequired, onClick: PropTypes.func.isRequired, onDoubleClick: PropTypes.func.isRequired, diff --git a/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx b/platform/ui/src/components/ToolbarButton/ToolbarButton.tsx similarity index 94% rename from platform/ui/src/components/ToolbarButton/ToolbarButton.jsx rename to platform/ui/src/components/ToolbarButton/ToolbarButton.tsx index 2f82c4257ab..3df22c90bde 100644 --- a/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx +++ b/platform/ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -9,8 +9,7 @@ const ToolbarButton = ({ id, icon, label, - commandName, - commandOptions, + commands, onInteraction, dropdownContent, // @@ -49,8 +48,7 @@ const ToolbarButton = ({ onInteraction({ itemId: id, interactionType: type, - commandName: commandName, - commandOptions: commandOptions, + commands, }); }} name={label} diff --git a/platform/ui/src/components/ToolbarButton/__stories__/toolbarButton.stories.mdx b/platform/ui/src/components/ToolbarButton/__stories__/toolbarButton.stories.mdx index 73edb98e91d..5efb2cde028 100644 --- a/platform/ui/src/components/ToolbarButton/__stories__/toolbarButton.stories.mdx +++ b/platform/ui/src/components/ToolbarButton/__stories__/toolbarButton.stories.mdx @@ -45,10 +45,10 @@ ToolbarButton is a component that renders the ToolbarButtons. dropdownContent: null, isActive: false, bState: { - primaryToolId: 'Wwwc', + primaryToolId: 'WindowLevel', toggles: {}, groups: { - primary: 'Wwwc', + primary: 'WindowLevel', }, }, }} diff --git a/platform/ui/src/components/Tooltip/Tooltip.jsx b/platform/ui/src/components/Tooltip/Tooltip.tsx similarity index 100% rename from platform/ui/src/components/Tooltip/Tooltip.jsx rename to platform/ui/src/components/Tooltip/Tooltip.tsx diff --git a/platform/ui/src/components/TooltipClipboard/TooltipClipboard.jsx b/platform/ui/src/components/TooltipClipboard/TooltipClipboard.tsx similarity index 100% rename from platform/ui/src/components/TooltipClipboard/TooltipClipboard.jsx rename to platform/ui/src/components/TooltipClipboard/TooltipClipboard.tsx diff --git a/platform/ui/src/components/Typography/Typography.jsx b/platform/ui/src/components/Typography/Typography.tsx similarity index 100% rename from platform/ui/src/components/Typography/Typography.jsx rename to platform/ui/src/components/Typography/Typography.tsx diff --git a/platform/ui/src/components/UserPreferences/UserPreferences.jsx b/platform/ui/src/components/UserPreferences/UserPreferences.tsx similarity index 100% rename from platform/ui/src/components/UserPreferences/UserPreferences.jsx rename to platform/ui/src/components/UserPreferences/UserPreferences.tsx diff --git a/platform/ui/src/components/UserPreferences/index.js b/platform/ui/src/components/UserPreferences/index.js index 857d525bb2c..4f63fac3667 100644 --- a/platform/ui/src/components/UserPreferences/index.js +++ b/platform/ui/src/components/UserPreferences/index.js @@ -1,3 +1,3 @@ -import UserPreferences from './UserPreferences.jsx'; +import UserPreferences from './UserPreferences.tsx'; export default UserPreferences; diff --git a/platform/ui/src/components/Viewport/Viewport.jsx b/platform/ui/src/components/Viewport/Viewport.tsx similarity index 100% rename from platform/ui/src/components/Viewport/Viewport.jsx rename to platform/ui/src/components/Viewport/Viewport.tsx diff --git a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.tsx similarity index 97% rename from platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx rename to platform/ui/src/components/ViewportActionBar/ViewportActionBar.tsx index faccf8eff65..8ca11ae58f8 100644 --- a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx +++ b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.tsx @@ -242,7 +242,7 @@ const ViewportActionBar = ({ let backgroundColor = '#020424'; if (useAltStyling || isTracked) { - backgroundColor = '#031923' + backgroundColor = '#031923'; } return ( @@ -336,7 +336,7 @@ ViewportActionBar.propTypes = { isTracked: PropTypes.bool.isRequired, isRehydratable: PropTypes.bool.isRequired, studyDate: PropTypes.string.isRequired, - currentSeries: PropTypes.number.isRequired, + currentSeries: PropTypes.string.isRequired, seriesDescription: PropTypes.string.isRequired, modality: PropTypes.string.isRequired, patientInformation: PropTypes.shape({ @@ -370,7 +370,7 @@ function PatientInfo({ isOpen, showPatientInfoRef, }) { - const { t } = useTranslation("PatientInfo") + const { t } = useTranslation('PatientInfo'); while (patientAge.charAt(0) === '0') { patientAge = patientAge.substr(1); @@ -397,7 +397,9 @@ function PatientInfo({
- {t('Sex')} + + {t('Sex')} +
- {t('Age')} + + {t('Age')} +
- {t('MRN')} + + {t('MRN')} + {MRN} diff --git a/platform/ui/src/components/ViewportDownloadForm/ViewportDownloadForm.jsx b/platform/ui/src/components/ViewportDownloadForm/ViewportDownloadForm.tsx similarity index 85% rename from platform/ui/src/components/ViewportDownloadForm/ViewportDownloadForm.jsx rename to platform/ui/src/components/ViewportDownloadForm/ViewportDownloadForm.tsx index fdc185436c2..e6ae4f4e63c 100644 --- a/platform/ui/src/components/ViewportDownloadForm/ViewportDownloadForm.jsx +++ b/platform/ui/src/components/ViewportDownloadForm/ViewportDownloadForm.tsx @@ -32,10 +32,11 @@ const FILE_TYPE_OPTIONS = [ ]; const DEFAULT_FILENAME = 'image'; -const REFRESH_VIEWPORT_TIMEOUT = 1000; + +const REFRESH_VIEWPORT_TIMEOUT = 100; const ViewportDownloadForm = ({ - activeViewport, + activeViewportElement, onClose, updateViewportPreview, enableViewport, @@ -180,13 +181,13 @@ const ViewportDownloadForm = ({ const loadAndUpdateViewports = useCallback(async () => { const { width: scaledWidth, height: scaledHeight } = await loadImage( - activeViewport, + activeViewportElement, viewportElement, dimensions.width, dimensions.height ); - toggleAnnotations(showAnnotations, viewportElement); + toggleAnnotations(showAnnotations, viewportElement, activeViewportElement); const scaledDimensions = { height: validSize(scaledHeight), @@ -217,7 +218,7 @@ const ViewportDownloadForm = ({ })); }, [ loadImage, - activeViewport, + activeViewportElement, viewportElement, dimensions.width, dimensions.height, @@ -247,7 +248,7 @@ const ViewportDownloadForm = ({ loadAndUpdateViewports(); }, REFRESH_VIEWPORT_TIMEOUT); }, [ - activeViewport, + activeViewportElement, viewportElement, showAnnotations, dimensions, @@ -275,7 +276,9 @@ const ViewportDownloadForm = ({ return (
- {t('Please specify the dimensions, filename, and desired type for the output image.')} + {t( + 'Please specify the dimensions, filename, and desired type for the output image.' + )}
@@ -284,7 +287,7 @@ const ViewportDownloadForm = ({ data-cy="file-name" value={filename} onChange={evt => setFilename(evt.target.value)} - label={t("File Name")} + label={t('File Name')} /> {renderErrorHandler('filename')}
@@ -296,9 +299,11 @@ const ViewportDownloadForm = ({ type="number" min={minimumSize} max={maximumSize} - label={t("Image width (px)")} + label={t('Image width (px)')} value={dimensions.width} - onChange={evt => onDimensionsChange(evt.target.value, 'width')} + onChange={evt => + onDimensionsChange(evt.target.value, 'width') + } data-cy="image-width" /> {renderErrorHandler('width')} @@ -308,9 +313,11 @@ const ViewportDownloadForm = ({ type="number" min={minimumSize} max={maximumSize} - label={t("Image height (px)")} + label={t('Image height (px)')} value={dimensions.height} - onChange={evt => onDimensionsChange(evt.target.value, 'height')} + onChange={evt => + onDimensionsChange(evt.target.value, 'height') + } data-cy="image-height" /> {renderErrorHandler('height')} @@ -337,7 +344,7 @@ const ViewportDownloadForm = ({
{}} > @@ -365,7 +372,7 @@ const ViewportDownloadForm = ({ checked={showAnnotations} onChange={event => setShowAnnotations(event.target.checked)} /> - {t("Show Annotations")} + {t('Show Annotations')}
@@ -373,41 +380,32 @@ const ViewportDownloadForm = ({
-
- {t("Image preview")} - {activeViewport && (
+ {t('Image preview')} + {activeViewportElement && ( +
setViewportElement(ref)} - > - -
)} - {!activeViewport && - - {t("Active viewport has no displayed image")} - - } -
+ >
+ )} + {!activeViewportElement && ( + + {t('Active viewport has no displayed image')} + + )} +
diff --git a/platform/ui/src/components/ViewportGrid/ViewportGrid.jsx b/platform/ui/src/components/ViewportGrid/ViewportGrid.tsx similarity index 66% rename from platform/ui/src/components/ViewportGrid/ViewportGrid.jsx rename to platform/ui/src/components/ViewportGrid/ViewportGrid.tsx index b638846d74b..79f3ceb2921 100644 --- a/platform/ui/src/components/ViewportGrid/ViewportGrid.jsx +++ b/platform/ui/src/components/ViewportGrid/ViewportGrid.tsx @@ -1,17 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -function ViewportGrid({ numRows, numCols, children }) { - const rowSize = 100 / numRows; - const colSize = 100 / numCols; - +function ViewportGrid({ numRows, numCols, layoutType, children }) { return (
{ + const topLeft = 'top-viewport left-viewport'; + const topRight = 'top-viewport right-viewport-scrollbar'; + const bottomRight = 'bottom-viewport right-viewport-scrollbar'; + const bottomLeft = 'bottom-viewport left-viewport'; + const overlay = 'absolute pointer-events-none'; + + return ( +
+
+ {props.topLeft} +
+
+ {props.topRight} +
+
+ {props.bottomRight} +
+
+ {props.bottomLeft} +
+
+ ); +}; + +export default ViewportOverlay; diff --git a/platform/ui/src/components/ViewportOverlay/index.js b/platform/ui/src/components/ViewportOverlay/index.js new file mode 100644 index 00000000000..af63b1853f3 --- /dev/null +++ b/platform/ui/src/components/ViewportOverlay/index.js @@ -0,0 +1,3 @@ +import ViewportOverlay from './ViewportOverlay'; + +export default ViewportOverlay; diff --git a/platform/ui/src/components/ViewportPane/ViewportPane.jsx b/platform/ui/src/components/ViewportPane/ViewportPane.tsx similarity index 79% rename from platform/ui/src/components/ViewportPane/ViewportPane.jsx rename to platform/ui/src/components/ViewportPane/ViewportPane.tsx index dfeaf00ee2a..39023564c14 100644 --- a/platform/ui/src/components/ViewportPane/ViewportPane.jsx +++ b/platform/ui/src/components/ViewportPane/ViewportPane.tsx @@ -9,6 +9,7 @@ import { useDrop } from 'react-dnd'; function ViewportPane({ children, className, + customStyle, isActive, onDrop, onDoubleClick, @@ -41,7 +42,7 @@ function ViewportPane({ } }; - const onInteractionHandler = (event) => { + const onInteractionHandler = event => { focus(); onInteraction(event); }; @@ -63,23 +64,29 @@ function ViewportPane({ onScroll={onInteractionHandler} onWheel={onInteractionHandler} className={classnames( - 'flex flex-col', - 'rounded-lg hover:border-primary-light transition duration-300 outline-none overflow-hidden', + 'w-full h-full rounded-lg overflow-hidden hover:border-primary-light transition duration-300 group', { 'border-2 border-primary-light': isActive, - 'border border-secondary-light': !isActive, + 'border-2 border-transparent': !isActive, }, className )} - // Normally, we'd use tailwindcss classes here, but margin and border classes use different units - // m-# (rem), border-# (px). To make sure we don't change the box size of our viewports - // and trigger a canvas resize, we have to use this little trick for margin. - // Assumes a :root font-fize of `16px` style={{ - margin: isActive ? '3px' : '4px', + ...customStyle, }} > - {children} +
+ {children} +
); } @@ -101,7 +108,7 @@ ViewportPane.propTypes = { onDoubleClick: PropTypes.func, }; -const noop = () => { }; +const noop = () => {}; ViewportPane.defaultProps = { onInteraction: noop, diff --git a/platform/ui/src/components/WindowLevelMenuItem/WindowLevelMenuItem.jsx b/platform/ui/src/components/WindowLevelMenuItem/WindowLevelMenuItem.tsx similarity index 100% rename from platform/ui/src/components/WindowLevelMenuItem/WindowLevelMenuItem.jsx rename to platform/ui/src/components/WindowLevelMenuItem/WindowLevelMenuItem.tsx diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js index 6b4bb9b87cb..213ddd6ea57 100644 --- a/platform/ui/src/components/index.js +++ b/platform/ui/src/components/index.js @@ -61,6 +61,8 @@ import UserPreferences from './UserPreferences'; import HotkeysPreferences from './HotkeysPreferences'; import HotkeyField from './HotkeyField'; import Header from './Header'; +import ImageScrollbar from './ImageScrollbar'; +import ViewportOverlay from './ViewportOverlay'; export { AboutModal, @@ -87,6 +89,7 @@ export { InputLabelWrapper, InputMultiSelect, InputText, + ImageScrollbar, Label, LayoutSelector, MeasurementTable, @@ -126,5 +129,6 @@ export { ViewportDownloadForm, ViewportGrid, ViewportPane, + ViewportOverlay, WindowLevelMenuItem, }; diff --git a/platform/ui/src/contextProviders/CineProvider.jsx b/platform/ui/src/contextProviders/CineProvider.tsx similarity index 77% rename from platform/ui/src/contextProviders/CineProvider.jsx rename to platform/ui/src/contextProviders/CineProvider.tsx index bdd913227bc..8db53ec2afc 100644 --- a/platform/ui/src/contextProviders/CineProvider.jsx +++ b/platform/ui/src/contextProviders/CineProvider.tsx @@ -13,7 +13,7 @@ const DEFAULT_STATE = { /* * 1: { isPlaying: false, frameRate: 24 }; */ - } + }, }; const DEFAULT_CINE = { isPlaying: false, frameRate: 24 }; @@ -29,7 +29,8 @@ export default function CineProvider({ children, service }) { if (!cines[id]) cines[id] = { id, ...DEFAULT_CINE }; cines[id].frameRate = frameRate || cines[id].frameRate; - cines[id].isPlaying = isPlaying !== undefined ? isPlaying : cines[id].isPlaying; + cines[id].isPlaying = + isPlaying !== undefined ? isPlaying : cines[id].isPlaying; return { ...state, ...{ cines } }; } @@ -41,15 +42,13 @@ export default function CineProvider({ children, service }) { } }; - const [state, dispatch] = useReducer( - reducer, - DEFAULT_STATE - ); + const [state, dispatch] = useReducer(reducer, DEFAULT_STATE); const getState = useCallback(() => state, [state]); const setIsCineEnabled = useCallback( - isCineEnabled => dispatch({ type: 'SET_IS_CINE_ENABLED', payload: isCineEnabled }), + isCineEnabled => + dispatch({ type: 'SET_IS_CINE_ENABLED', payload: isCineEnabled }), [dispatch] ); @@ -75,23 +74,19 @@ export default function CineProvider({ children, service }) { if (service) { service.setServiceImplementation({ getState, setIsCineEnabled, setCine }); } - }, [ - getState, - service, - setCine, - setIsCineEnabled, - ]); + }, [getState, service, setCine, setIsCineEnabled]); const api = { getState, setCine, setIsCineEnabled, + playClip: (element, playClipOptions) => + service.playClip(element, playClipOptions), + stopClip: element => service.stopClip(element), }; return ( - - {children} - + {children} ); } diff --git a/platform/ui/src/contextProviders/DialogProvider.jsx b/platform/ui/src/contextProviders/DialogProvider.tsx similarity index 100% rename from platform/ui/src/contextProviders/DialogProvider.jsx rename to platform/ui/src/contextProviders/DialogProvider.tsx index a187ddca0ee..2c62ee98ae4 100644 --- a/platform/ui/src/contextProviders/DialogProvider.jsx +++ b/platform/ui/src/contextProviders/DialogProvider.tsx @@ -52,17 +52,6 @@ const DialogProvider = ({ children, service }) => { }; }; - /** - * Sets the implementation of a dialog service that can be used by extensions. - * - * @returns void - */ - useEffect(() => { - if (service) { - service.setServiceImplementation({ create, dismiss, dismissAll }); - } - }, [create, dismiss, service]); - /** * UI Dialog * @@ -80,8 +69,6 @@ const DialogProvider = ({ children, service }) => { * @property {Function} onDrag Called while dragging. */ - useEffect(() => _bringToFront(lastDialogId), [_bringToFront, lastDialogId]); - /** * Creates a new dialog and return its id. * @@ -146,6 +133,19 @@ const DialogProvider = ({ children, service }) => { }); }, []); + /** + * Sets the implementation of a dialog service that can be used by extensions. + * + * @returns void + */ + useEffect(() => { + if (service) { + service.setServiceImplementation({ create, dismiss, dismissAll }); + } + }, [create, dismiss, service]); + + useEffect(() => _bringToFront(lastDialogId), [_bringToFront, lastDialogId]); + const renderDialogs = () => dialogs.map(dialog => { const { diff --git a/platform/ui/src/contextProviders/DragAndDropProvider.jsx b/platform/ui/src/contextProviders/DragAndDropProvider.tsx similarity index 100% rename from platform/ui/src/contextProviders/DragAndDropProvider.jsx rename to platform/ui/src/contextProviders/DragAndDropProvider.tsx diff --git a/platform/ui/src/contextProviders/ImageViewerProvider.jsx b/platform/ui/src/contextProviders/ImageViewerProvider.tsx similarity index 100% rename from platform/ui/src/contextProviders/ImageViewerProvider.jsx rename to platform/ui/src/contextProviders/ImageViewerProvider.tsx diff --git a/platform/ui/src/contextProviders/ModalComponent.jsx b/platform/ui/src/contextProviders/ModalComponent.tsx similarity index 100% rename from platform/ui/src/contextProviders/ModalComponent.jsx rename to platform/ui/src/contextProviders/ModalComponent.tsx diff --git a/platform/ui/src/contextProviders/ModalProvider.jsx b/platform/ui/src/contextProviders/ModalProvider.tsx similarity index 100% rename from platform/ui/src/contextProviders/ModalProvider.jsx rename to platform/ui/src/contextProviders/ModalProvider.tsx diff --git a/platform/ui/src/contextProviders/SnackbarProvider.jsx b/platform/ui/src/contextProviders/SnackbarProvider.tsx similarity index 100% rename from platform/ui/src/contextProviders/SnackbarProvider.jsx rename to platform/ui/src/contextProviders/SnackbarProvider.tsx index 0d106016930..1303fd6dd55 100644 --- a/platform/ui/src/contextProviders/SnackbarProvider.jsx +++ b/platform/ui/src/contextProviders/SnackbarProvider.tsx @@ -27,17 +27,6 @@ const SnackbarProvider = ({ children, service }) => { const [count, setCount] = useState(1); const [snackbarItems, setSnackbarItems] = useState([]); - /** - * Sets the implementation of a notification service that can be used by extensions. - * - * @returns void - */ - useEffect(() => { - if (service) { - service.setServiceImplementation({ hide, show }); - } - }, [service, hide, show]); - const show = useCallback( options => { if (!options || (!options.title && !options.message)) { @@ -104,6 +93,17 @@ const SnackbarProvider = ({ children, service }) => { }; } + /** + * Sets the implementation of a notification service that can be used by extensions. + * + * @returns void + */ + useEffect(() => { + if (service) { + service.setServiceImplementation({ hide, show }); + } + }, [service, hide, show]); + return ( {!!snackbarItems && } diff --git a/platform/ui/src/contextProviders/UserAuthenticationProvider.js b/platform/ui/src/contextProviders/UserAuthenticationProvider.tsx similarity index 100% rename from platform/ui/src/contextProviders/UserAuthenticationProvider.js rename to platform/ui/src/contextProviders/UserAuthenticationProvider.tsx diff --git a/platform/ui/src/contextProviders/ViewportDialogProvider.jsx b/platform/ui/src/contextProviders/ViewportDialogProvider.tsx similarity index 100% rename from platform/ui/src/contextProviders/ViewportDialogProvider.jsx rename to platform/ui/src/contextProviders/ViewportDialogProvider.tsx diff --git a/platform/ui/src/contextProviders/ViewportGridProvider.jsx b/platform/ui/src/contextProviders/ViewportGridProvider.tsx similarity index 57% rename from platform/ui/src/contextProviders/ViewportGridProvider.jsx rename to platform/ui/src/contextProviders/ViewportGridProvider.tsx index c4707235b04..3ddeab2db0b 100644 --- a/platform/ui/src/contextProviders/ViewportGridProvider.jsx +++ b/platform/ui/src/contextProviders/ViewportGridProvider.tsx @@ -7,23 +7,39 @@ import React, { } from 'react'; import PropTypes from 'prop-types'; +import viewportLabels from '../utils/viewportLabels'; + const DEFAULT_STATE = { - // starting from null, hanging - // protocol will defined number of rows and cols numRows: null, numCols: null, + layoutType: 'grid', viewports: [ - /* - * { - * displaySetInstanceUID: string, - * } - */ + { + displaySetInstanceUIDs: [], + viewportOptions: {}, + displaySetOptions: [{}], + x: 0, // left + y: 0, // top + width: 100, + height: 100, + viewportLabel: null, + }, ], activeViewportIndex: 0, }; export const ViewportGridContext = createContext(DEFAULT_STATE); +/** + * Given the flatten index, and rows and column, it returns the + * row and column index + */ +const unravelIndex = (index, numRows, numCols) => { + const row = Math.floor(index / numCols); + const col = index % numCols; + return { row, col }; +}; + export function ViewportGridProvider({ children, service }) { const viewportGridReducer = (state, action) => { switch (action.type) { @@ -31,16 +47,31 @@ export function ViewportGridProvider({ children, service }) { return { ...state, ...{ activeViewportIndex: action.payload } }; } case 'SET_DISPLAYSET_FOR_VIEWPORT': { - const { viewportIndex, displaySetInstanceUID } = action.payload; + const { + viewportIndex, + displaySetInstanceUIDs, + viewportOptions, + displaySetOptions, + } = action.payload; const viewports = state.viewports.slice(); - viewports[viewportIndex] = { displaySetInstanceUID }; + // merge the displaySetOptions and viewportOptions and displaySetInstanceUIDs + // into the viewport object at the given index + viewports[viewportIndex] = { + ...viewports[viewportIndex], + displaySetInstanceUIDs, + viewportOptions, + displaySetOptions, + viewportLabel: viewportLabels[viewportIndex], + }; return { ...state, ...{ viewports }, cachedLayout: null }; } case 'SET_LAYOUT': { - const { numCols, numRows } = action.payload; - const numPanes = numCols * numRows; + const { numCols, numRows, layoutType, layoutOptions } = action.payload; + + // If empty viewportOptions, we use numRow and numCols to calculate number of viewports + const numPanes = layoutOptions.length || numRows * numCols; const viewports = state.viewports.slice(); const activeViewportIndex = state.activeViewportIndex >= numPanes ? 0 : state.activeViewportIndex; @@ -52,9 +83,34 @@ export function ViewportGridProvider({ children, service }) { viewports.pop(); } + for (let i = 0; i < numPanes; i++) { + let xPos, yPos, w, h; + + if (layoutOptions && layoutOptions[i]) { + ({ x: xPos, y: yPos, width: w, height: h } = layoutOptions[i]); + } else { + const { row, col } = unravelIndex(i, numRows, numCols); + w = 1 / numCols; + h = 1 / numRows; + xPos = col * w; + yPos = row * h; + } + + viewports[i].width = w; + viewports[i].height = h; + viewports[i].x = xPos; + viewports[i].y = yPos; + } + return { ...state, - ...{ activeViewportIndex, numCols, numRows, viewports }, + ...{ + activeViewportIndex, + numCols, + numRows, + layoutType, + viewports, + }, cachedLayout: null, }; } @@ -62,10 +118,17 @@ export function ViewportGridProvider({ children, service }) { return { numCols: null, numRows: null, + layoutType: 'grid', activeViewportIndex: 0, viewports: [ { - displaySetInstanceUID: null, + displaySetInstanceUIDs: [], + displaySetOptions: [], + viewportOptions: {}, + x: 0, // left + y: 0, // top + width: 100, + height: 100, }, ], cachedLayout: null, @@ -99,25 +162,34 @@ export function ViewportGridProvider({ children, service }) { [dispatch] ); - const setDisplaysetForViewport = useCallback( - ({ viewportIndex, displaySetInstanceUID }) => + const setDisplaySetsForViewport = useCallback( + ({ + viewportIndex, + displaySetInstanceUIDs, + viewportOptions = {}, + displaySetOptions = [{}], + }) => dispatch({ type: 'SET_DISPLAYSET_FOR_VIEWPORT', payload: { viewportIndex, - displaySetInstanceUID, + displaySetInstanceUIDs, + viewportOptions, + displaySetOptions, }, }), [dispatch] ); const setLayout = useCallback( - ({ numCols, numRows }) => + ({ layoutType, numRows, numCols, layoutOptions = [] }) => dispatch({ type: 'SET_LAYOUT', payload: { - numCols, + layoutType, numRows, + numCols, + layoutOptions, }, }), [dispatch] @@ -160,7 +232,7 @@ export function ViewportGridProvider({ children, service }) { service.setServiceImplementation({ getState, setActiveViewportIndex, - setDisplaysetForViewport, + setDisplaySetsForViewport, setLayout, reset, setCachedLayout, @@ -171,7 +243,7 @@ export function ViewportGridProvider({ children, service }) { getState, service, setActiveViewportIndex, - setDisplaysetForViewport, + setDisplaySetsForViewport, setLayout, reset, setCachedLayout, @@ -181,7 +253,7 @@ export function ViewportGridProvider({ children, service }) { const api = { getState, setActiveViewportIndex, - setDisplaysetForViewport, + setDisplaySetsForViewport, setLayout, setCachedLayout, reset, diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js index d80db595de9..22661ea1389 100644 --- a/platform/ui/src/index.js +++ b/platform/ui/src/index.js @@ -96,6 +96,8 @@ export { ViewportGrid, ViewportPane, WindowLevelMenuItem, + ImageScrollbar, + ViewportOverlay, } from './components'; /** These are mostly used in the docs */ diff --git a/platform/ui/src/pages/Colors/BackgroundColor.jsx b/platform/ui/src/pages/Colors/BackgroundColor.tsx similarity index 100% rename from platform/ui/src/pages/Colors/BackgroundColor.jsx rename to platform/ui/src/pages/Colors/BackgroundColor.tsx diff --git a/platform/ui/src/utils/viewportLabels.ts b/platform/ui/src/utils/viewportLabels.ts new file mode 100644 index 00000000000..5e0e3015a55 --- /dev/null +++ b/platform/ui/src/utils/viewportLabels.ts @@ -0,0 +1,30 @@ +const viewportLabels = [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', +]; + +export default viewportLabels; diff --git a/platform/ui/src/views/Viewer/components/Header.js b/platform/ui/src/views/Viewer/components/Header.js index 7859beff632..5d63d6b8bd2 100644 --- a/platform/ui/src/views/Viewer/components/Header.js +++ b/platform/ui/src/views/Viewer/components/Header.js @@ -1,78 +1,7 @@ -import React, { useState } from 'react'; +import React from 'react'; import { NavBar, Svg, Icon, IconButton } from '../../../components'; const Header = () => { - const [activeTool, setActiveTool] = useState('Zoom'); - const dropdownContent = [ - { - name: 'Soft tissue', - value: '400/40', - }, - { name: 'Lung', value: '1500 / -600' }, - { name: 'Liver', value: '150 / 90' }, - { name: 'Bone', value: '2500 / 480' }, - { name: 'Brain', value: '80 / 40' }, - ]; - const tools = [ - { - id: 'Zoom', - label: 'Zoom', - icon: 'tool-zoom', - commandName: 'setToolActive', - commandOptions: { toolName: 'Zoom' }, - onClick: () => setActiveTool('Zoom'), - }, - { - id: 'Wwwc', - label: 'Levels', - icon: 'tool-window-level', - commandName: 'setToolActive', - commandOptions: { toolName: 'Wwwc' }, - onClick: () => setActiveTool('Wwwc'), - dropdownContent: ( -
- {dropdownContent.map((row, i) => ( -
-
- {row.name} - - {row.value} - -
- {i} -
- ))} -
- ), - }, - { - id: 'Pan', - label: 'Pan', - icon: 'tool-move', - commandName: 'setToolActive', - commandOptions: { toolName: 'Pan' }, - onClick: () => setActiveTool('Pan'), - }, - { - id: 'Capture', - label: 'Capture', - icon: 'tool-capture', - commandName: 'setToolActive', - commandOptions: { toolName: 'Capture' }, - onClick: () => setActiveTool('Capture'), - }, - { - id: 'Layout', - label: 'Layout', - icon: 'tool-layout', - commandName: 'setToolActive', - commandOptions: { toolName: 'Layout' }, - onClick: () => setActiveTool('Layout'), - }, - ]; return (
diff --git a/platform/viewer/.webpack/webpack.pwa.js b/platform/viewer/.webpack/webpack.pwa.js index aad4acab1c6..3436933ee09 100644 --- a/platform/viewer/.webpack/webpack.pwa.js +++ b/platform/viewer/.webpack/webpack.pwa.js @@ -25,7 +25,7 @@ const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`; const Dotenv = require('dotenv-webpack'); const writePluginImportFile = require('./writePluginImportsFile.js'); -writePluginImportFile(SRC_DIR); +const copyPluginFromExtensions = writePluginImportFile(SRC_DIR, DIST_DIR); const setHeaders = (res, path) => { if (path.indexOf('.gz') !== -1) { @@ -35,6 +35,8 @@ const setHeaders = (res, path) => { } if (path.indexOf('.pdf') !== -1) { res.setHeader('Content-Type', 'application/pdf'); + } else if (path.indexOf('/frames') !== -1) { + res.setHeader('Content-Type', 'multipart/related') } else { res.setHeader('Content-Type', 'application/json') } @@ -77,6 +79,7 @@ module.exports = (env, argv) => { // Copy "Public" Folder to Dist new CopyWebpackPlugin({ patterns: [ + ...copyPluginFromExtensions, { from: PUBLIC_DIR, to: DIST_DIR, @@ -108,13 +111,14 @@ module.exports = (env, argv) => { PUBLIC_URL: PUBLIC_URL, }, }), - // No longer maintained; but good for generating icons + manifest - // new FaviconsWebpackPlugin( path.join(PUBLIC_DIR, 'assets', 'icons-512.png')), + // Generate a service worker for fast local loads new InjectManifest({ swDest: 'sw.js', swSrc: path.join(SRC_DIR, 'service-worker.js'), // Increase the limit to 4mb: - maximumFileSizeToCacheInBytes: 4 * 1024 * 1024, + maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, + // Need to exclude the theme as it is updated independently + exclude: [/theme/], }), new CopyPlugin({ patterns: [ @@ -140,16 +144,6 @@ module.exports = (env, argv) => { overlay: { errors: true, warnings: false }, }, 'static': [ - { - directory: path.join(require('os').homedir(), 'dicomweb'), - staticOptions: { - extensions: ['gz', 'br'], - index: "index.json.gz", - redirect: true, - setHeaders, - }, - publicPath: '/dicomweb', - }, { directory: '../../testdata', staticOptions: { diff --git a/platform/viewer/.webpack/writePluginImportsFile.js b/platform/viewer/.webpack/writePluginImportsFile.js index 2590b5b6c70..5cc82ec40a9 100644 --- a/platform/viewer/.webpack/writePluginImportsFile.js +++ b/platform/viewer/.webpack/writePluginImportsFile.js @@ -13,6 +13,8 @@ function constructLines(input, categoryName) { addToWindowLines: [], }; + if (!input) return lines; + input.forEach(entry => { const packageName = entry.packageName; @@ -22,7 +24,7 @@ function constructLines(input, categoryName) { `import ${defaultImportName} from '${packageName}';\n` ); lines.addToWindowLines.push( - `window.${categoryName}.push(${defaultImportName});\n` + `${categoryName}.push(${defaultImportName});\n` ); pluginCount++; @@ -42,7 +44,11 @@ function getFormattedImportBlock(importLines) { } function getFormattedWindowBlock(addToWindowLines) { - let content = `window.extensions = [];\nwindow.modes = [];\n\n`; + let content = "const extensions = [];\n" + + "const modes = [];\n" + + "const modesFactory = [];\n" + + "window.extensions = extensions;\n" + + "window.modes = modes;\n\n"; addToWindowLines.forEach(addToWindowLine => { content += addToWindowLine; @@ -51,21 +57,49 @@ function getFormattedWindowBlock(addToWindowLines) { return content; } -function writePluginImportsFile(SRC_DIR) { +function getRuntimeLoadModesExtensions() { + return "\n\n// Add a dynamic runtime loader\n" + + "export default async () => {\n" + + " for(const modeFactory of modesFactory) {\n" + + " const newModes = await modeFactory(modes,extensions);\n" + + " newModes.forEach(newMode => modes.push(newMode));\n" + + "}\n}\n"; +} + +const createCopyPluginFromExtensions = (SRC_DIR, DIST_DIR, plugins) => { + + return plugins.map(plugin => { + const from = `${SRC_DIR}/../node_modules/${plugin.packageName}/public/`; + const exists = fs.existsSync(from); + return exists ? { + from, + to: DIST_DIR, + toType: 'dir', + } : undefined; + } + ).filter(x => !!x); +} + +function writePluginImportsFile(SRC_DIR, DIST_DIR) { let pluginImportsJsContent = autogenerationDisclaimer; const extensionLines = constructLines(pluginConfig.extensions, 'extensions'); const modeLines = constructLines(pluginConfig.modes, 'modes'); + const modesFactoryLines = constructLines(pluginConfig.modesFactory, 'modesFactory'); pluginImportsJsContent += getFormattedImportBlock([ ...extensionLines.importLines, ...modeLines.importLines, + ...modesFactoryLines.importLines, ]); pluginImportsJsContent += getFormattedWindowBlock([ ...extensionLines.addToWindowLines, ...modeLines.addToWindowLines, + ...modesFactoryLines.addToWindowLines, ]); + pluginImportsJsContent += getRuntimeLoadModesExtensions(); + fs.writeFileSync( `${SRC_DIR}/pluginImports.js`, pluginImportsJsContent, @@ -77,6 +111,18 @@ function writePluginImportsFile(SRC_DIR) { } } ); + + const copyPluginFromExtensions = createCopyPluginFromExtensions( + SRC_DIR, DIST_DIR, + [ + ...pluginConfig.modesFactory, + ...pluginConfig.modes, + ...pluginConfig.extensions, + ...pluginConfig.umd, + ] + ) + + return copyPluginFromExtensions; } module.exports = writePluginImportsFile; diff --git a/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js b/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js index 3390114a841..13468105b44 100644 --- a/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js +++ b/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneHotkeys.spec.js @@ -3,6 +3,17 @@ describe('OHIF Cornerstone Hotkeys', () => { cy.checkStudyRouteInViewer( '1.2.840.113619.2.5.1762583153.215519.978957063.78' ); + + cy.window() + .its('cornerstone') + .then(cornerstone => { + // For debugging issues where tests pass locally but fail on CI + // - Sometimes Cypress orb seems to use CPU rendering pathway + cy.log( + `Cornerstone using CPU Rendering?: ${cornerstone.getShouldUseCPURendering()}` + ); + }); + cy.expectMinimumThumbnails(3); }); @@ -33,55 +44,55 @@ describe('OHIF Cornerstone Hotkeys', () => { }); it('checks if hotkeys "V" and "H" can flip the image', () => { - // Hotkey V - cy.get('body').type('V'); - cy.get('@viewportInfoMidLeft').should('contains.text', 'L'); - cy.get('@viewportInfoMidTop').should('contains.text', 'A'); // Hotkey H cy.get('body').type('H'); cy.get('@viewportInfoMidLeft').should('contains.text', 'L'); + cy.get('@viewportInfoMidTop').should('contains.text', 'A'); + // Hotkey V + cy.get('body').type('V'); + cy.get('@viewportInfoMidLeft').should('contains.text', 'L'); cy.get('@viewportInfoMidTop').should('contains.text', 'P'); }); - it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => { - //Click on button and verify if icon is active on toolbar - cy.get('@zoomBtn') - .click() - .then($zoomBtn => { - cy.wrap($zoomBtn).should('have.class', 'active'); - }); - - // Hotkey + - cy.get('body').type('+++'); // Press hotkey 3 times - cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.35x'); - // Hotkey - - cy.get('body').type('-'); - cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.20x'); - // Hotkey = - cy.get('body').type('='); - cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.90x'); - }); - - it('checks if hotkey "SPACEBAR" can reset the image', () => { - //Click on button and verify if icon is active on toolbar - cy.get('@zoomBtn') - .click() - .then($zoomBtn => { - cy.wrap($zoomBtn).should('have.class', 'active'); - }); - - // Press multiples hotkeys - cy.get('body').type('V+++I'); - cy.get('@viewportInfoMidLeft').should('contains.text', 'L'); - cy.get('@viewportInfoMidTop').should('contains.text', 'A'); - cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.35x'); + // it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => { + // //Click on button and verify if icon is active on toolbar + // cy.get('@zoomBtn') + // .click() + // .then($zoomBtn => { + // cy.wrap($zoomBtn).should('have.class', 'active'); + // }); + + // // Hotkey + + // cy.get('body').type('+++'); // Press hotkey 3 times + // cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.30x'); + // // Hotkey - + // cy.get('body').type('-'); + // cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.09x'); + // // Hotkey = + // cy.get('body').type('='); + // cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.67x'); + // }); - // Hotkey SPACEBAR - cy.get('body').type(' '); - cy.get('@viewportInfoMidLeft').should('contains.text', 'R'); - cy.get('@viewportInfoMidTop').should('contains.text', 'A'); - cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.90x'); - }); + // it('checks if hotkey "SPACEBAR" can reset the image', () => { + // //Click on button and verify if icon is active on toolbar + // cy.get('@zoomBtn') + // .click() + // .then($zoomBtn => { + // cy.wrap($zoomBtn).should('have.class', 'active'); + // }); + + // // Press multiples hotkeys + // cy.get('body').type('V+++I'); + // cy.get('@viewportInfoMidLeft').should('contains.text', 'L'); + // cy.get('@viewportInfoMidTop').should('contains.text', 'A'); + // cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.30x'); + + // // Hotkey SPACEBAR + // cy.get('body').type(' '); + // cy.get('@viewportInfoMidLeft').should('contains.text', 'R'); + // cy.get('@viewportInfoMidTop').should('contains.text', 'A'); + // cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.67x'); + // }); /* // TODO: Pretty sure this is not implemented yet diff --git a/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js b/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js index 73239f020f2..175ded66a03 100644 --- a/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js +++ b/platform/viewer/cypress/integration/measurement-tracking/OHIFCornerstoneToolbar.spec.js @@ -42,31 +42,37 @@ describe('OHIF Cornerstone Toolbar', () => { //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') - .trigger('mousedown', 'center', { which: 1 }) - .trigger('mousemove', 'top', { which: 1 }) + .trigger('mousedown', 'center', { buttons: 1 }) + .trigger('mousemove', 'top', { buttons: 1 }) .trigger('mouseup'); const expectedText = 'Ser: 1Img: 1 1/26256 x 256Loc: -30.00 mm Thick: 5.00 mm'; cy.get('@viewportInfoBottomLeft').should('have.text', expectedText); });*/ - it('checks if Zoom tool will zoom in/out an image in the viewport', () => { - //Click on button and verify if icon is active on toolbar - cy.get('@zoomBtn') - .click() - .then($zoomBtn => { - cy.wrap($zoomBtn).should('have.class', 'active'); - }); - - //drags the mouse inside the viewport to be able to interact with series - cy.get('@viewport') - .trigger('mousedown', 'center', { which: 1 }) - .trigger('mousemove', 'top', { which: 1 }) - .trigger('mouseup'); - - const expectedText = 'Zoom:0.45x'; - cy.get('@viewportInfoTopLeft').should('have.text', expectedText); - }); + // it('checks if Zoom tool will zoom in/out an image in the viewport', () => { + // //Click on button and verify if icon is active on toolbar + // cy.get('@zoomBtn') + // .click() + // .then($zoomBtn => { + // cy.wrap($zoomBtn).should('have.class', 'active'); + // }); + + // // IMPORTANT: Cypress sends out a mouseEvent which doesn't have the buttons + // // property. This is a workaround to simulate a mouseEvent with the buttons property + // // which is consumed by cornerstone + // cy.get('@viewport') + // .trigger('mousedown', 'center', { buttons: 1 }) + // .trigger('mousemove', 'top', { + // buttons: 1, + // }) + // .trigger('mouseup', { + // buttons: 1, + // }); + + // const expectedText = 'Zoom:0.96x'; + // cy.get('@viewportInfoTopLeft').should('have.text', expectedText); + // }); it('checks if Levels tool will change the window width and center of an image', () => { //Click on button and verify if icon is active on toolbar @@ -78,14 +84,14 @@ describe('OHIF Cornerstone Toolbar', () => { //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') - .trigger('mousedown', 'center', { which: 1 }) - .trigger('mousemove', 'top', { which: 1 }) - .trigger('mouseup') - .trigger('mousedown', 'center', { which: 1 }) - .trigger('mousemove', 'left', { which: 1 }) - .trigger('mouseup'); - - const expectedText = 'W:731L:226'; + .click({ force: true }) + .trigger('mousedown', 'center', { buttons: 1 }) + // Since we have scrollbar on the right side of the viewport, we need to + // force the mousemove since it goes to another element + .trigger('mousemove', 'right', { buttons: 1, force: true }) + .trigger('mouseup', { buttons: 1 }); + + const expectedText = 'W:1930L:479'; cy.get('@viewportInfoTopLeft').should('have.text', expectedText); }); @@ -98,8 +104,8 @@ describe('OHIF Cornerstone Toolbar', () => { }); cy.get('@viewport') - .trigger('mousedown', 'center', { which: 1 }) - .trigger('mousemove', 'bottom', { which: 1 }) + .trigger('mousedown', 'center', { buttons: 1 }) + .trigger('mousemove', 'bottom', { buttons: 1 }) .trigger('mouseup', 'bottom'); }); @@ -222,6 +228,7 @@ describe('OHIF Cornerstone Toolbar', () => { }); });*/ + /** it('checks if More button will prompt a modal with secondary tools', () => { //Click on More button cy.get('@moreBtnSecondary').click(); @@ -244,6 +251,7 @@ describe('OHIF Cornerstone Toolbar', () => { // Verify if overlay is hidden cy.get('@toolbarOverlay').should('not.be.visible'); }); + */ /*it('checks if Layout tool will multiply the number of viewports displayed', () => { //Click on Layout button and verify if overlay is displayed diff --git a/platform/viewer/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js b/platform/viewer/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js index 65a59b62488..06f27cf162d 100644 --- a/platform/viewer/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js +++ b/platform/viewer/cypress/integration/measurement-tracking/OHIFDownloadSnapshotFile.spec.js @@ -10,10 +10,11 @@ describe('OHIF Download Snapshot File', () => { cy.openDownloadImageModal(); }); - /*afterEach(() => { + /** + afterEach(() => { // Close modal - // TODO: Modal.jsx currently uses Icon instead of IconButton, so + // TODO: Modal.tsx currently uses Icon instead of IconButton, so // it's not as easy to give it the cypress data attribute. // We should fix that first cy.get('[data-cy="close-button"]') @@ -21,7 +22,7 @@ describe('OHIF Download Snapshot File', () => { .click(); });*/ - it('checks displayed information for Desktop experience', function() { + /*it('checks displayed information for Desktop experience', function() { // Set Desktop resolution cy.viewport(1750, 720); // Visual comparison @@ -29,26 +30,25 @@ describe('OHIF Download Snapshot File', () => { //Check if all elements are displayed // TODO: need to add this attribute to the modal - /*cy.get('[data-cy=modal-header]') + cy.get('[data-cy=modal-header]') .as('downloadImageModal') - .should('contain.text', 'Download High Quality Image');*/ + .should('contain.text', 'Download High Quality Image'); // Check input fields // TODO: select2 - /*cy.get('[data-cy="file-type"]') + cy.get('[data-cy="file-type"]') .select('png') .should('have.value', 'png') .select('jpg') - .should('have.value', 'jpg');*/ + .should('have.value', 'jpg'); // Check image preview cy.get('[data-cy="image-preview"]').should('contain.text', 'Image preview'); - - /* TODO: This is a canvas now, not an img with src + TODO: This is a canvas now, not an img with src cy.get('[data-cy="viewport-preview-img"]') .should('have.attr', 'src') - .and('include', 'data:image');*/ + .and('include', 'data:image'); // Check buttons cy.get('[data-cy="cancel-btn"]') @@ -58,6 +58,7 @@ describe('OHIF Download Snapshot File', () => { .scrollIntoView() .should('be.visible'); }); + */ /*it('cancel changes on download modal', function() { //Change Image Width, Filename and File Type diff --git a/platform/viewer/cypress/integration/measurement-tracking/OHIFStudyViewer.spec.js b/platform/viewer/cypress/integration/measurement-tracking/OHIFStudyViewer.spec.js index d0abc80344a..f17b8946b84 100644 --- a/platform/viewer/cypress/integration/measurement-tracking/OHIFStudyViewer.spec.js +++ b/platform/viewer/cypress/integration/measurement-tracking/OHIFStudyViewer.spec.js @@ -125,7 +125,6 @@ describe('OHIF Study Viewer Page', function() { }); */ - /*it('checks if measurement item can be deleted through the context menu on the viewport', function() { cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport @@ -267,33 +266,32 @@ describe('OHIF Study Viewer Page', function() { //cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText); }); - it('performs right click to zoom', function() { - // This is not used to activate the tool, it is used to ensure the - // top left viewport info shows the zoom values (it only shows up - // when the zoom tool is active) - cy.get('@zoomBtn') - .click() - .then($zoomBtn => { - cy.wrap($zoomBtn).should('have.class', 'active'); - }); - - //Right click on viewport - cy.get('@viewport') - .trigger('mousedown', 'top', { which: 3 }) - .trigger('mousemove', 'center', { which: 3 }) - .trigger('mouseup'); - - - const expectedText = 'Zoom:3.98x'; - cy.get('@viewportInfoTopLeft').should('contains.text', expectedText); - }); - - it('performs middle click to pan', function() { + // it('performs right click to zoom', function() { + // // This is not used to activate the tool, it is used to ensure the + // // top left viewport info shows the zoom values (it only shows up + // // when the zoom tool is active) + // cy.get('@zoomBtn') + // .click() + // .then($zoomBtn => { + // cy.wrap($zoomBtn).should('have.class', 'active'); + // }); + + // //Right click on viewport + // cy.get('@viewport') + // .trigger('mousedown', 'top', { buttons: 2 }) + // .trigger('mousemove', 'center', { buttons: 2 }) + // .trigger('mouseup'); + + // const expectedText = 'Zoom:6.65x'; + // cy.get('@viewportInfoTopLeft').should('contains.text', expectedText); + // }); + + /*it('performs middle click to pan', function() { //Get image position from cornerstone and check if y axis was modified let cornerstone; let currentPan; - // TO DO: Replace the cornerstone pan check by Percy snapshop comparison + // TO DO: Replace the cornerstone pan check by Percy snapshot comparison cy.window() .its('cornerstone') .then(c => { @@ -304,13 +302,13 @@ describe('OHIF Study Viewer Page', function() { //pan image with middle click cy.get('@viewport') - .trigger('mousedown', 'center', { which: 2 }) - .trigger('mousemove', 'bottom', { which: 2 }) + .trigger('mousedown', 'center', { buttons: 3 }) + .trigger('mousemove', 'bottom', { buttons: 3 }) .trigger('mouseup', 'bottom') .then(() => { expect(currentPan().y > 0).to.eq(true); }); - }); + });*/ /*it('opens About modal and verify the displayed information', function() { cy.get('[data-cy="options-dropdown"]') diff --git a/platform/viewer/cypress/support/aliases.js b/platform/viewer/cypress/support/aliases.js index fbe1ab992ce..f1eba6ddb89 100644 --- a/platform/viewer/cypress/support/aliases.js +++ b/platform/viewer/cypress/support/aliases.js @@ -2,22 +2,28 @@ export function initCornerstoneToolsAliases() { cy.get('[data-cy="StackScroll"]').as('stackScrollBtn'); cy.get('[data-cy="Zoom"]').as('zoomBtn'); - cy.get('[data-cy="Wwwc-split-button-primary"]').as('wwwcBtnPrimary'); - cy.get('[data-cy="Wwwc-split-button-secondary"]').as('wwwcBtnSecondary'); + cy.get('[data-cy="WindowLevel-split-button-primary"]').as('wwwcBtnPrimary'); + cy.get('[data-cy="WindowLevel-split-button-secondary"]').as( + 'wwwcBtnSecondary' + ); cy.get('[data-cy="Pan"]').as('panBtn'); - cy.get('[data-cy="MeasurementTools-split-button-primary"]').as('measurementToolsBtnPrimary'); - cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as('measurementToolsBtnSecondary'); - cy.get('[data-cy="Angle"]').as('angleBtn'); + cy.get('[data-cy="MeasurementTools-split-button-primary"]').as( + 'measurementToolsBtnPrimary' + ); + cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as( + 'measurementToolsBtnSecondary' + ); + // cy.get('[data-cy="Angle"]').as('angleBtn'); cy.get('[data-cy="MoreTools-split-button-primary"]').as('moreBtnPrimary'); cy.get('[data-cy="MoreTools-split-button-secondary"]').as('moreBtnSecondary'); cy.get('[data-cy="Layout"]').as('layoutBtn'); - cy.get('.viewport-element').as('viewport'); + cy.get('.cornerstone-viewport-element').as('viewport'); } //Creating aliases for Common page elements export function initCommonElementsAliases() { cy.get('[data-cy="trackedMeasurements-btn"]').as('measurementsBtn'); - cy.get('.viewport-element').as('viewport'); + cy.get('.cornerstone-viewport-element').as('viewport'); cy.get('[data-cy="seriesList-btn"]').as('seriesBtn'); // TODO: Panels are not in DOM when closed, move this somewhere else @@ -25,8 +31,12 @@ export function initCommonElementsAliases() { cy.get('[data-cy="studyBrowser-panel"]').as('seriesPanel'); cy.get('[data-cy="viewport-overlay-top-right"]').as('viewportInfoTopRight'); cy.get('[data-cy="viewport-overlay-top-left"]').as('viewportInfoTopLeft'); - cy.get('[data-cy="viewport-overlay-bottom-right"]').as('viewportInfoBottomRight'); - cy.get('[data-cy="viewport-overlay-bottom-left"]').as('viewportInfoBottomLeft'); + cy.get('[data-cy="viewport-overlay-bottom-right"]').as( + 'viewportInfoBottomRight' + ); + cy.get('[data-cy="viewport-overlay-bottom-left"]').as( + 'viewportInfoBottomLeft' + ); cy.get('.left-mid.orientation-marker').as('viewportInfoMidLeft'); cy.get('.top-mid.orientation-marker').as('viewportInfoMidTop'); diff --git a/platform/viewer/cypress/support/commands.js b/platform/viewer/cypress/support/commands.js index 30da0fcec99..5148417379e 100644 --- a/platform/viewer/cypress/support/commands.js +++ b/platform/viewer/cypress/support/commands.js @@ -89,7 +89,7 @@ Cypress.Commands.add('openStudyModality', Modality => { /** * Command to wait and check if a new page was loaded * - * @param {string} url - part of the expected url. Default value is /viewer/ + * @param {string} url - part of the expected url. Default value is /viewer */ Cypress.Commands.add('isPageLoaded', (url = '/viewer') => { return cy.location('pathname', { timeout: 60000 }).should('include', url); @@ -139,9 +139,11 @@ Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => { // TODO: Added a wait which appears necessary in Cornerstone Tools >4? cy.wrap($viewport) - .click(x1, y1).wait(100) + .click(x1, y1) + .wait(100) .trigger('mousemove', { clientX: x2, clientY: y2 }) - .click(x2, y2).wait(100); + .click(x2, y2) + .wait(100); }); }); @@ -173,9 +175,11 @@ Cypress.Commands.add( ); Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => { - cy.get('[data-cy="study-browser-thumbnail"]', { timeout: 50000 }).should($itemList => { - expect($itemList.length >= seriesToWait).to.be.true; - }); + cy.get('[data-cy="study-browser-thumbnail"]', { timeout: 50000 }).should( + $itemList => { + expect($itemList.length >= seriesToWait).to.be.true; + } + ); }); //Command to wait DICOM image to load into the viewport @@ -190,9 +194,12 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => { const onEvent = renderedEvt => { const element = renderedEvt.detail.element; - element.removeEventListener('cornerstoneimagerendered', onEvent); - $cornerstone.events.removeEventListener( - 'cornerstoneimagerendered', + element.removeEventListener( + $cornerstone.Enums.Events.IMAGE_RENDERED, + onEvent + ); + $cornerstone.eventTarget.removeEventListener( + $cornerstone.Enums.Events.IMAGE_RENDERED, onEvent ); resolve(); @@ -200,16 +207,24 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => { const onEnabled = enabledEvt => { const element = enabledEvt.detail.element; - element.addEventListener('cornerstoneimagerendered', onEvent); + element.addEventListener( + $cornerstone.Enums.Events.IMAGE_RENDERED, + onEvent + ); + + $cornerstone.eventTarget.removeEventListener( + $cornerstone.Enums.Events.ELEMENT_ENABLED, + onEnabled + ); }; const enabledElements = $cornerstone.getEnabledElements(); - if (enabledElements && enabledElements.length && !enabledElements[0].invalid) { + if (enabledElements && enabledElements.length) { // Sometimes the page finishes rendering before this gets run, // if so, just resolve immediately. resolve(); } else { - $cornerstone.events.addEventListener( - 'cornerstoneelementenabled', + $cornerstone.eventTarget.addEventListener( + $cornerstone.Enums.Events.ELEMENT_ENABLED, onEnabled ); } @@ -224,8 +239,7 @@ Cypress.Commands.add('resetViewport', () => { cy.get('[data-cy="MoreTools-split-button-primary"]') .should('have.attr', 'data-tool', 'Reset') .as('moreBtn') - .click() - ; + .click(); }); Cypress.Commands.add('imageZoomIn', () => { @@ -234,8 +248,8 @@ Cypress.Commands.add('imageZoomIn', () => { //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') - .trigger('mousedown', 'top', { which: 1 }) - .trigger('mousemove', 'center', { which: 1 }) + .trigger('mousedown', 'top', { buttons: 1 }) + .trigger('mousemove', 'center', { buttons: 1 }) .trigger('mouseup'); }); @@ -245,8 +259,8 @@ Cypress.Commands.add('imageContrast', () => { //drags the mouse inside the viewport to be able to interact with series cy.get('@viewport') - .trigger('mousedown', 'center', { which: 1 }) - .trigger('mousemove', 'top', { which: 1 }) + .trigger('mousedown', 'center', { buttons: 1 }) + .trigger('mousemove', 'top', { buttons: 1 }) .trigger('mouseup'); }); @@ -509,13 +523,12 @@ Cypress.Commands.add( (function_label, shortcut) => { // Within scopes all `.get` and `.contains` to within the matched elements // dom instead of checking from document - cy.get('.HotkeysPreferences') - .within(() => { - cy.contains(function_label) // label we're looking for - .parent() - .find('input') // closest input to that label - .type(shortcut, { force: true }); // Set new shortcut for that function - }); + cy.get('.HotkeysPreferences').within(() => { + cy.contains(function_label) // label we're looking for + .parent() + .find('input') // closest input to that label + .type(shortcut, { force: true }); // Set new shortcut for that function + }); } ); diff --git a/platform/viewer/netlify.toml b/platform/viewer/netlify.toml index 6949be0913c..4a2f7190c83 100644 --- a/platform/viewer/netlify.toml +++ b/platform/viewer/netlify.toml @@ -21,7 +21,7 @@ # If 'production', `yarn install` does not install devDependencies NODE_ENV = "development" NODE_VERSION = "14.19.1" - YARN_VERSION = "1.22.0" + YARN_VERSION = "1.22.5" RUBY_VERSION = "2.6.2" YARN_FLAGS = "--no-ignore-optional --pure-lockfile" NETLIFY_USE_YARN = "true" diff --git a/platform/viewer/package.json b/platform/viewer/package.json index f55e304457c..049fcb29bd3 100644 --- a/platform/viewer/package.json +++ b/platform/viewer/package.json @@ -1,7 +1,7 @@ { "name": "@ohif/viewer", "version": "5.0.0", - "productVersion": "3.0.8", + "productVersion": "3.2.0", "description": "OHIF Viewer", "author": "OHIF Contributors", "license": "MIT", @@ -30,6 +30,7 @@ "dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js", "dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js", "dev:viewer": "yarn run dev", + "preinstall": "node preinstall.js", "start": "yarn run dev", "test:e2e": "cypress open", "test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'", @@ -44,30 +45,30 @@ "dist", "README.md" ], - "peerDependencies": { - "cornerstone-core": "^2.6.0" - }, "dependencies": { "@babel/runtime": "7.16.3", "@ohif/core": "^3.0.0", "@ohif/extension-cornerstone": "^3.0.0", + "@ohif/extension-cornerstone-dicom-sr": "^3.0.0", "@ohif/extension-default": "^3.0.0", - "@ohif/extension-dicom-sr": "^3.0.0", - "@ohif/extension-measurement-tracking": "^3.0.0", + "@ohif/extension-dicom-pdf": "^3.0.1", + "@ohif/extension-dicom-video": "^3.0.1", "@ohif/i18n": "^1.0.0", + "@ohif/mode-basic-dev-mode": "^3.0.0", "@ohif/mode-longitudinal": "^3.0.0", "@ohif/ui": "^2.0.0", "@types/react": "^16.0.0", "classnames": "^2.2.6", + "config-point": "^0.4.8", "core-js": "^3.16.1", "cornerstone-math": "^0.1.9", - "cornerstone-tools": "6.0.2", - "cornerstone-wado-image-loader": "4.0.4", - "dcmjs": "0.16.1", + "cornerstone-wado-image-loader": "^4.2.0", + "dcmjs": "^0.24.5", + "detect-gpu": "^4.0.16", "dicom-parser": "^1.8.9", "dotenv-webpack": "^1.7.0", "hammerjs": "^2.0.8", - "history": "^5.2.0", + "history": "^5.3.0", "i18next": "^17.0.3", "i18next-browser-languagedetector": "^3.0.1", "lodash.isequal": "4.5.0", @@ -79,13 +80,13 @@ "react-dom": "^17.0.2", "react-dropzone": "^10.1.7", "react-i18next": "^10.11.0", - "react-resize-detector": "^4.2.0", - "react-router": "^6.2.1", - "react-router-dom": "^6.2.1" + "react-resize-detector": "^6.7.6", + "react-router": "^6.3.0", + "react-router-dom": "^6.3.0" }, "devDependencies": { - "@percy/cypress": "^2.3.0", - "cypress": "^6.4.0", + "@percy/cypress": "^3.1.1", + "cypress": "^9.5.4", "cypress-file-upload": "^3.5.3", "identity-obj-proxy": "3.0.x", "lodash": "4.17.15", diff --git a/platform/viewer/pluginConfig.json b/platform/viewer/pluginConfig.json index a5caf24bdc6..3e98435146f 100644 --- a/platform/viewer/pluginConfig.json +++ b/platform/viewer/pluginConfig.json @@ -9,7 +9,7 @@ "version": "3.0.0" }, { - "packageName": "@ohif/extension-dicom-sr", + "packageName": "@ohif/extension-cornerstone-dicom-sr", "version": "3.0.0" }, { @@ -23,6 +23,10 @@ { "packageName": "@ohif/extension-dicom-video", "version": "3.0.1" + }, + { + "packageName": "@ohif/extension-tmtv", + "version": "3.0.0" } ], "modes": [ @@ -30,5 +34,9 @@ "packageName": "@ohif/mode-longitudinal", "version": "3.0.0" } + ], + "modesFactory": [ + ], + "umd": [ ] } diff --git a/platform/viewer/preinstall.js b/platform/viewer/preinstall.js new file mode 100644 index 00000000000..458e41fcc24 --- /dev/null +++ b/platform/viewer/preinstall.js @@ -0,0 +1,13 @@ +console.log('preinstall.js'); + +const { exec } = require('child_process'); +const log = (err, stdout, stderr) => console.log(stdout); + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +if (GITHUB_TOKEN) { + const command = `git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/".insteadOf ssh://git@github.com/`; + console.log(command); + exec(command, log); +} else { + console.log('No GITHUB_TOKEN found, skipping private repo customization'); +} diff --git a/platform/viewer/public/_headers b/platform/viewer/public/_headers new file mode 100644 index 00000000000..dfe145ba770 --- /dev/null +++ b/platform/viewer/public/_headers @@ -0,0 +1,6 @@ +/* + # Ensure the examples are cross-origin isolated so that + # we can use SharedArrayBuffer + # See https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated + Cross-Origin-Embedder-Policy: require-corp + Cross-Origin-Opener-Policy: same-origin diff --git a/platform/viewer/public/config/aws.js b/platform/viewer/public/config/aws.js index d209de51fe9..4de6f2b5ba9 100644 --- a/platform/viewer/public/config/aws.js +++ b/platform/viewer/public/config/aws.js @@ -83,12 +83,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js index ccad643070f..eaf2947b60c 100644 --- a/platform/viewer/public/config/default.js +++ b/platform/viewer/public/config/default.js @@ -4,6 +4,12 @@ window.config = { extensions: [], modes: [], showStudyList: true, + maxNumberOfWebWorkers: 3, + maxNumRequests: { + interaction: 100, + thumbnail: 75, + prefetch: 10, + }, // filterQueryParam: false, dataSources: [ { @@ -81,12 +87,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/viewer/public/config/demo.js b/platform/viewer/public/config/demo.js index 8de0415804d..f91c342e703 100644 --- a/platform/viewer/public/config/demo.js +++ b/platform/viewer/public/config/demo.js @@ -31,12 +31,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/viewer/public/config/dicomweb_relative.js b/platform/viewer/public/config/dicomweb_relative.js new file mode 100644 index 00000000000..634d853859c --- /dev/null +++ b/platform/viewer/public/config/dicomweb_relative.js @@ -0,0 +1,160 @@ +window.config = { + routerBasename: '/', + // whiteLabelling: {}, + extensions: [], + modes: [], + showStudyList: true, + maxNumberOfWebWorkers: 3, + // filterQueryParam: false, + dataSources: [ + { + friendlyName: 'Static WADO Local Data', + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + name: 'DCM4CHEE', + wadoUriRoot: '/dicomweb', + qidoRoot: '/dicomweb', + wadoRoot: '/dicomweb', + qidoSupportsIncludeField: false, + supportsReject: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video,pdf', + }, + }, + { + friendlyName: 'dicom json', + namespace: '@ohif/extension-default.dataSourcesModule.dicomjson', + sourceName: 'dicomjson', + configuration: { + name: 'json', + }, + }, + { + friendlyName: 'dicom local', + namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal', + sourceName: 'dicomlocal', + configuration: {}, + }, + ], + httpErrorHandler: error => { + // This is 429 when rejected from the public idc sandbox too often. + console.warn(error.status); + + // Could use services manager here to bring up a dialog/modal if needed. + console.warn('test, navigate to https://ohif.org/'); + }, + // whiteLabeling: { + // /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */ + // createLogoComponentFn: function (React) { + // return React.createElement( + // 'a', + // { + // target: '_self', + // rel: 'noopener noreferrer', + // className: 'text-purple-600 line-through', + // href: '/', + // }, + // React.createElement('img', + // { + // src: './customLogo.svg', + // className: 'w-8 h-8', + // } + // )) + // }, + // }, + defaultDataSourceName: 'dicomweb', + hotkeys: [ + { + commandName: 'incrementActiveViewport', + label: 'Next Viewport', + keys: ['right'], + }, + { + commandName: 'decrementActiveViewport', + label: 'Previous Viewport', + keys: ['left'], + }, + { commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] }, + { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, + { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, + { + commandName: 'flipViewportVertical', + label: 'Flip Horizontally', + keys: ['h'], + }, + { + commandName: 'flipViewportHorizontal', + label: 'Flip Vertically', + keys: ['v'], + }, + { commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] }, + { commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] }, + { commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] }, + { commandName: 'resetViewport', label: 'Reset', keys: ['space'] }, + { commandName: 'nextImage', label: 'Next Image', keys: ['down'] }, + { commandName: 'previousImage', label: 'Previous Image', keys: ['up'] }, + { + commandName: 'previousViewportDisplaySet', + label: 'Previous Series', + keys: ['pagedown'], + }, + { + commandName: 'nextViewportDisplaySet', + label: 'Next Series', + keys: ['pageup'], + }, + { commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] }, + // ~ Window level presets + { + commandName: 'windowLevelPreset1', + label: 'W/L Preset 1', + keys: ['1'], + }, + { + commandName: 'windowLevelPreset2', + label: 'W/L Preset 2', + keys: ['2'], + }, + { + commandName: 'windowLevelPreset3', + label: 'W/L Preset 3', + keys: ['3'], + }, + { + commandName: 'windowLevelPreset4', + label: 'W/L Preset 4', + keys: ['4'], + }, + { + commandName: 'windowLevelPreset5', + label: 'W/L Preset 5', + keys: ['5'], + }, + { + commandName: 'windowLevelPreset6', + label: 'W/L Preset 6', + keys: ['6'], + }, + { + commandName: 'windowLevelPreset7', + label: 'W/L Preset 7', + keys: ['7'], + }, + { + commandName: 'windowLevelPreset8', + label: 'W/L Preset 8', + keys: ['8'], + }, + { + commandName: 'windowLevelPreset9', + label: 'W/L Preset 9', + keys: ['9'], + }, + ], +}; diff --git a/platform/viewer/public/config/e2e.js b/platform/viewer/public/config/e2e.js index c3c2cb49fbf..d166fdf282a 100644 --- a/platform/viewer/public/config/e2e.js +++ b/platform/viewer/public/config/e2e.js @@ -102,12 +102,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/viewer/public/config/local_static.js b/platform/viewer/public/config/local_static.js index bd998fc0e61..d377776a96e 100644 --- a/platform/viewer/public/config/local_static.js +++ b/platform/viewer/public/config/local_static.js @@ -4,6 +4,7 @@ window.config = { extensions: [], modes: [], showStudyList: true, + maxNumberOfWebWorkers: 3, // filterQueryParam: false, dataSources: [ { @@ -12,9 +13,9 @@ window.config = { sourceName: 'dicomweb', configuration: { name: 'DCM4CHEE', - wadoUriRoot: '/dicomweb', - qidoRoot: '/dicomweb', - wadoRoot: '/dicomweb', + wadoUriRoot: 'http://localhost:5000/dicomweb', + qidoRoot: 'http://localhost:5000/dicomweb', + wadoRoot: 'http://localhost:5000/dicomweb', qidoSupportsIncludeField: false, supportsReject: false, imageRendering: 'wadors', @@ -83,12 +84,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/viewer/public/config/netlify.js b/platform/viewer/public/config/netlify.js index ccad643070f..711f78b40a3 100644 --- a/platform/viewer/public/config/netlify.js +++ b/platform/viewer/public/config/netlify.js @@ -19,7 +19,7 @@ window.config = { supportsReject: true, imageRendering: 'wadors', thumbnailRendering: 'wadors', - enableStudyLazyLoad: true, + enableStudyLazyLoad: false, supportsFuzzyMatching: true, supportsWildcard: true, }, @@ -81,12 +81,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/viewer/public/config/public_dicomweb.js b/platform/viewer/public/config/public_dicomweb.js index dd2a54b0111..d7cbf153bd6 100644 --- a/platform/viewer/public/config/public_dicomweb.js +++ b/platform/viewer/public/config/public_dicomweb.js @@ -35,12 +35,12 @@ window.config = { { commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] }, { commandName: 'invertViewport', label: 'Invert', keys: ['i'] }, { - commandName: 'flipViewportVertical', + commandName: 'flipViewportHorizontal', label: 'Flip Horizontally', keys: ['h'], }, { - commandName: 'flipViewportHorizontal', + commandName: 'flipViewportVertical', label: 'Flip Vertically', keys: ['v'], }, diff --git a/platform/viewer/public/theme/theme.json5 b/platform/viewer/public/theme/theme.json5 deleted file mode 100644 index 4964b47bb28..00000000000 --- a/platform/viewer/public/theme/theme.json5 +++ /dev/null @@ -1,3 +0,0 @@ -{ - // Currently empty because nothing is configurable yet with themes -} diff --git a/platform/viewer/src/App.jsx b/platform/viewer/src/App.tsx similarity index 88% rename from platform/viewer/src/App.jsx rename to platform/viewer/src/App.tsx index 7cd27c8c85c..dd754780dce 100644 --- a/platform/viewer/src/App.jsx +++ b/platform/viewer/src/App.tsx @@ -1,10 +1,10 @@ // External -import React from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import i18n from '@ohif/i18n'; import { I18nextProvider } from 'react-i18next'; import { BrowserRouter } from 'react-router-dom'; -import Compose from './routes/Mode/Compose.js'; +import Compose from './routes/Mode/Compose.tsx'; import { DialogProvider, @@ -22,12 +22,26 @@ import { import { AppConfigProvider } from '@state'; import createRoutes from './routes'; import appInit from './appInit.js'; -import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes.jsx'; +import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes.tsx'; let commandsManager, extensionManager, servicesManager, hotkeysManager; function App({ config, defaultExtensions, defaultModes }) { - const init = appInit(config, defaultExtensions, defaultModes); + const [init, setInit] = useState(null); + + useEffect(() => { + const run = async () => { + appInit(config, defaultExtensions, defaultModes) + .then(setInit) + .catch(console.error); + }; + + run(); + }, []); + + if (!init) { + return null; + } // Set above for named export commandsManager = init.commandsManager; @@ -45,7 +59,9 @@ function App({ config, defaultExtensions, defaultModes }) { dataSources, extensionManager, servicesManager, + commandsManager, hotkeysManager, + commandsManager, routerBasename, }); const { diff --git a/platform/viewer/src/appInit.js b/platform/viewer/src/appInit.js index 360893c8cdb..47589ffa739 100644 --- a/platform/viewer/src/appInit.js +++ b/platform/viewer/src/appInit.js @@ -12,6 +12,7 @@ import { ToolBarService, ViewportGridService, HangingProtocolService, + SegmentationService, CineService, UserAuthenticationService, errorHandler, @@ -22,7 +23,7 @@ import { * @param {object|func} appConfigOrFunc - application configuration, or a function that returns application configuration * @param {object[]} defaultExtensions - array of extension objects */ -function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { +async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { const appConfig = { ...(typeof appConfigOrFunc === 'function' ? appConfigOrFunc({ servicesManager }) @@ -31,12 +32,6 @@ function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { const commandsManagerConfig = { getAppState: () => {}, - /** Used by commands to determine active context */ - getActiveContexts: () => [ - 'VIEWER', - 'DEFAULT', - 'ACTIVE_VIEWPORT::CORNERSTONE', - ], }; const commandsManager = new CommandsManager(commandsManagerConfig); @@ -59,6 +54,7 @@ function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { ToolBarService, ViewportGridService, HangingProtocolService, + SegmentationService, CineService, UserAuthenticationService, ]); @@ -73,7 +69,7 @@ function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { * Example: [ext1, ext2, ext3] * Example2: [[ext1, config], ext2, [ext3, config]] */ - extensionManager.registerExtensions( + await extensionManager.registerExtensions( [...defaultExtensions, ...appConfig.extensions], appConfig.dataSources ); diff --git a/platform/viewer/src/components/EmptyViewport.jsx b/platform/viewer/src/components/EmptyViewport.tsx similarity index 100% rename from platform/viewer/src/components/EmptyViewport.jsx rename to platform/viewer/src/components/EmptyViewport.tsx diff --git a/platform/viewer/src/components/ViewportGrid.jsx b/platform/viewer/src/components/ViewportGrid.jsx deleted file mode 100644 index 518fc2e731f..00000000000 --- a/platform/viewer/src/components/ViewportGrid.jsx +++ /dev/null @@ -1,286 +0,0 @@ -/** - * CSS Grid Reference: http://grid.malven.co/ - */ -import React, { useEffect, useCallback } from 'react'; -import PropTypes from 'prop-types'; -import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui'; -import EmptyViewport from './EmptyViewport'; -import classNames from 'classnames'; - -function ViewerViewportGrid(props) { - const { servicesManager, viewportComponents, dataSource } = props; - const [viewportGrid, viewportGridService] = useViewportGrid(); - - const { - numCols, - numRows, - activeViewportIndex, - viewports, - cachedLayout, - } = viewportGrid; - - // TODO -> Need some way of selecting which displaySets hit the viewports. - const { - DisplaySetService, - MeasurementService, - HangingProtocolService, - } = servicesManager.services; - - - const updateDisplaysetForViewports = useCallback( - (displaySets) => { - const [ - matchDetails, - hpAlreadyApplied, - ] = HangingProtocolService.getState(); - - if (!matchDetails.length) return; - // Match each viewport individually - - const numViewports = viewportGrid.numRows * viewportGrid.numCols; - for (let i = 0; i < numViewports; i++) { - if (hpAlreadyApplied[i] === true) { - continue; - } - - // if current viewport doesn't have a match - if (matchDetails[i] === undefined) return - - const { SeriesInstanceUID } = matchDetails[i]; - const matchingDisplaySet = displaySets.find(ds => { - return ds.SeriesInstanceUID === SeriesInstanceUID; - }); - - if (!matchingDisplaySet) { - continue; - } - - viewportGridService.setDisplaysetForViewport({ - viewportIndex: i, - displaySetInstanceUID: matchingDisplaySet.displaySetInstanceUID, - }); - - HangingProtocolService.setHangingProtocolAppliedForViewport(i); - } - }, - [viewportGrid, numRows, numCols], - ) - - // Using Hanging protocol engine to match the displaySets - useEffect(() => { - const { unsubscribe } = DisplaySetService.subscribe( - DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, - eventData => { - const { displaySetsAdded } = eventData; - updateDisplaysetForViewports(displaySetsAdded) - } - ); - - return () => { - unsubscribe(); - }; - }, [viewportGrid]); - - - - // Changing the Hanging protocol while viewing - useEffect(() => { - const displaySets = DisplaySetService.getActiveDisplaySets(); - updateDisplaysetForViewports(displaySets) - }, [viewportGrid]) - - - - - // Layout change based on hanging protocols - useEffect(() => { - const { unsubscribe } = HangingProtocolService.subscribe( - HangingProtocolService.EVENTS.NEW_LAYOUT, - ({ numRows, numCols }) => { - viewportGridService.setLayout({ numRows, numCols }); - } - ); - - return () => { - unsubscribe(); - }; - }, [viewports]); - - - useEffect(() => { - const { unsubscribe } = MeasurementService.subscribe( - MeasurementService.EVENTS.JUMP_TO_MEASUREMENT, - ({ viewportIndex, measurement }) => { - const referencedDisplaySetInstanceUID = - measurement.displaySetInstanceUID; - - const viewportsDisplaySetInstanceUIDs = viewports.map( - vp => vp.displaySetInstanceUID - ); - - // if we already have the displayset in one of the viewports - if ( - viewportsDisplaySetInstanceUIDs.indexOf( - referencedDisplaySetInstanceUID - ) > -1 - ) { - return; - } - - // If not in any of the viewports, hang it inside the active viewport - viewportGridService.setDisplaysetForViewport({ - viewportIndex, - displaySetInstanceUID: referencedDisplaySetInstanceUID, - }); - } - ); - - return () => { - unsubscribe(); - }; - }, [viewports]); - - const onDoubleClick = viewportIndex => { - // TODO -> Disabled for now. - // onNewImage on a cornerstone viewport is firing setDisplaySetForViewport. - // Which it really really shouldn't. We need a larger fix for jump to - // measurements and all cornerstone "imageIndex" state to fix this. - if (cachedLayout) { - viewportGridService.set({ - numCols: cachedLayout.numCols, - numRows: cachedLayout.numRows, - activeViewportIndex: cachedLayout.activeViewportIndex, - viewports: cachedLayout.viewports, - cachedLayout: null, - }); - - return; - } - - const cachedViewports = viewports.map(viewport => { - return { - displaySetInstanceUID: viewport.displaySetInstanceUID, - }; - }); - - viewportGridService.set({ - numCols: 1, - numRows: 1, - activeViewportIndex: 0, - viewports: [ - { - displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID, - imageIndex: undefined, - }, - ], - cachedLayout: { - numCols, - numRows, - viewports: cachedViewports, - activeViewportIndex: viewportIndex, - }, - }); - }; - - const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => { - viewportGridService.setDisplaysetForViewport({ - viewportIndex, - displaySetInstanceUID, - }); - }; - - const getViewportPanes = () => { - const viewportPanes = []; - const numViewportPanes = numCols * numRows; - - for (let i = 0; i < numViewportPanes; i++) { - const viewportIndex = i; - const isActive = activeViewportIndex === viewportIndex; - const paneMetadata = viewports[i] || {}; - const { displaySetInstanceUID } = paneMetadata; - - const displaySet = - DisplaySetService.getDisplaySetByUID(displaySetInstanceUID) || {}; - - const ViewportComponent = _getViewportComponent( - displaySet.SOPClassHandlerId, - viewportComponents - ); - - const onInterationHandler = event => { - if (isActive) return; - - if (event) { - event.preventDefault(); - event.stopPropagation(); - } - - viewportGridService.setActiveViewportIndex(viewportIndex); - }; - - // TEMP -> Double click disabled for now - // onDoubleClick={() => onDoubleClick(viewportIndex)} - - viewportPanes[i] = ( - -
- -
-
- ); - } - - return viewportPanes; - }; - - if (!numCols || !numCols) { - return null; - } - - return ( - - {/* {ViewportPanes} */} - {getViewportPanes()} - - ); -} - -ViewerViewportGrid.propTypes = { - viewportComponents: PropTypes.array.isRequired, -}; - -ViewerViewportGrid.defaultProps = { - viewportComponents: [], -}; - -function _getViewportComponent(SOPClassHandlerId, viewportComponents) { - if (!SOPClassHandlerId) { - return EmptyViewport; - } - - for (let i = 0; i < viewportComponents.length; i++) { - if ( - viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId) - ) { - const { component } = viewportComponents[i]; - return component; - } - } -} - -export default ViewerViewportGrid; diff --git a/platform/viewer/src/components/ViewportGrid.tsx b/platform/viewer/src/components/ViewportGrid.tsx new file mode 100644 index 00000000000..e57d633ce53 --- /dev/null +++ b/platform/viewer/src/components/ViewportGrid.tsx @@ -0,0 +1,381 @@ +import React, { useEffect, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui'; +import EmptyViewport from './EmptyViewport'; +import classNames from 'classnames'; + +function ViewerViewportGrid(props) { + const { servicesManager, viewportComponents, dataSource } = props; + const [viewportGrid, viewportGridService] = useViewportGrid(); + + const { numCols, numRows, activeViewportIndex, viewports } = viewportGrid; + + // TODO -> Need some way of selecting which displaySets hit the viewports. + const { + DisplaySetService, + MeasurementService, + HangingProtocolService, + } = servicesManager.services; + + /** + * This callback runs only after displaySets have changed (created and added or modified) + */ + const updateDisplaySetsForViewports = useCallback( + availableDisplaySets => { + if (!availableDisplaySets.length) { + return; + } + + const [ + matchDetails, + hpAlreadyApplied, + ] = HangingProtocolService.getState(); + + if (!matchDetails.length) { + return; + } + + // Match each viewport individually + const numViewports = viewportGrid.numRows * viewportGrid.numCols; + for (let i = 0; i < numViewports; i++) { + if (hpAlreadyApplied[i] === true) { + continue; + } + + // if current viewport doesn't have a match + if (matchDetails[i] === undefined) return; + + const { displaySetsInfo, viewportOptions } = matchDetails[i]; + + const displaySetUIDsToHang = []; + const displaySetUIDsToHangOptions = []; + displaySetsInfo.forEach(({ SeriesInstanceUID, displaySetOptions }) => { + const matchingDisplaySet = availableDisplaySets.find(ds => { + return ds.SeriesInstanceUID === SeriesInstanceUID; + }); + + if (!matchingDisplaySet) { + return; + } + + displaySetUIDsToHang.push(matchingDisplaySet.displaySetInstanceUID); + displaySetUIDsToHangOptions.push(displaySetOptions); + }); + + if (!displaySetUIDsToHang.length) { + continue; + } + + viewportGridService.setDisplaySetsForViewport({ + viewportIndex: i, + displaySetInstanceUIDs: displaySetUIDsToHang, + viewportOptions, + displaySetOptions: displaySetUIDsToHangOptions, + }); + + // During setting displaySets for viewport, we need to update the hanging protocol + // but some viewports contain more than one display set (fusion), and their displaySet + // will not be available at the time of setting displaySets for viewport. So we need to + // update the hanging protocol after making sure all the matched display sets are available + // and set on the viewport + if (displaySetUIDsToHang.length === displaySetsInfo.length) { + // The following will set the hpAlreadyApplied state + HangingProtocolService.setHangingProtocolAppliedForViewport(i); + } + } + }, + [viewportGrid, numRows, numCols] + ); + + useEffect(() => { + const displaySets = DisplaySetService.getActiveDisplaySets(); + updateDisplaySetsForViewports(displaySets); + }, [numRows, numCols]); + + // Layout change based on hanging protocols + useEffect(() => { + const { unsubscribe } = HangingProtocolService.subscribe( + HangingProtocolService.EVENTS.NEW_LAYOUT, + ({ layoutType, numRows, numCols, layoutOptions }) => { + viewportGridService.setLayout({ + numRows, + numCols, + layoutType, + layoutOptions, + }); + } + ); + + return () => { + unsubscribe(); + }; + }, [viewports]); + + // Using Hanging protocol engine to match the displaySets + useEffect(() => { + const { unsubscribe } = DisplaySetService.subscribe( + DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED, + activeDisplaySets => { + updateDisplaySetsForViewports(activeDisplaySets); + } + ); + + return () => { + unsubscribe(); + }; + }, [viewports]); + + useEffect(() => { + const { unsubscribe } = MeasurementService.subscribe( + MeasurementService.EVENTS.JUMP_TO_MEASUREMENT, + ({ viewportIndex, measurement }) => { + const referencedDisplaySetInstanceUID = + measurement.displaySetInstanceUID; + + // if we already have the displaySet in one of the viewports + // Todo: handle fusion display sets? + for (const viewport of viewports) { + const isMatch = viewport.displaySetInstanceUIDs.includes( + referencedDisplaySetInstanceUID + ); + if (isMatch) { + return; + } + } + + const displaySet = DisplaySetService.getDisplaySetByUID( + referencedDisplaySetInstanceUID + ); + + let imageIndex; + // jump straight to the initial image index if we can + if (displaySet.images && measurement.SOPInstanceUID) { + imageIndex = displaySet.images.findIndex( + image => image.SOPInstanceUID === measurement.SOPInstanceUID + ); + } + + // If not in any of the viewports, hang it inside the active viewport + viewportGridService.setDisplaySetsForViewport({ + viewportIndex, + displaySetInstanceUIDs: [referencedDisplaySetInstanceUID], + viewportOptions: { + initialImageOptions: { + index: imageIndex, + }, + }, + }); + } + ); + + return () => { + unsubscribe(); + }; + }, [viewports]); + + /** + //Changing the Hanging protocol while viewing + useEffect(() => { + const displaySets = DisplaySetService.getActiveDisplaySets(); + updateDisplaySetsForViewports(displaySets); + }, [viewports]); + + + // subscribe to displayset metadata changes + useEffect(() => { + const { unsubscribe } = DisplaySetService.subscribe( + DisplaySetService.EVENTS.DISPLAY_SETS_METADATA_UPDATED, + displaySets => { + // Todo: properly refresh the viewportGrid to use the new displaySet + // with the new metadata. + setState({}); + } + ); + return () => { + unsubscribe(); + }; + }, [viewports]); + + const onDoubleClick = viewportIndex => { + // TODO -> Disabled for now. + // onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport. + // Which it really really shouldn't. We need a larger fix for jump to + // measurements and all cornerstone "imageIndex" state to fix this. + if (cachedLayout) { + viewportGridService.set({ + numCols: cachedLayout.numCols, + numRows: cachedLayout.numRows, + activeViewportIndex: cachedLayout.activeViewportIndex, + viewports: cachedLayout.viewports, + cachedLayout: null, + }); + + return; + } + + const cachedViewports = viewports.map(viewport => { + return { + displaySetInstanceUID: viewport.displaySetInstanceUID, + }; + }); + + viewportGridService.set({ + numCols: 1, + numRows: 1, + activeViewportIndex: 0, + viewports: [ + { + displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID, + imageIndex: undefined, + }, + ], + cachedLayout: { + numCols, + numRows, + viewports: cachedViewports, + activeViewportIndex: viewportIndex, + }, + }); + }; + + */ + + const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => { + viewportGridService.setDisplaySetsForViewport({ + viewportIndex, + displaySetInstanceUIDs: [displaySetInstanceUID], + }); + }; + + const getViewportPanes = useCallback(() => { + const viewportPanes = []; + + for (let i = 0; i < viewports.length; i++) { + const viewportIndex = i; + const isActive = activeViewportIndex === viewportIndex; + const paneMetadata = viewports[i] || {}; + const { + displaySetInstanceUIDs, + viewportOptions, + displaySetOptions, // array of options for each display set in the viewport + x: viewportX, + y: viewportY, + width: viewportWidth, + height: viewportHeight, + viewportLabel, + } = paneMetadata; + + const displaySetInstanceUIDsToUse = displaySetInstanceUIDs || []; + + // This is causing the viewport components re-render when the activeViewportIndex changes + const displaySets = displaySetInstanceUIDsToUse.map( + displaySetInstanceUID => { + return ( + DisplaySetService.getDisplaySetByUID(displaySetInstanceUID) || {} + ); + } + ); + + const ViewportComponent = _getViewportComponent( + displaySets, + viewportComponents + ); + + // look inside displaySets to see if they need reRendering + const displaySetsNeedsRerendering = displaySets.some(displaySet => { + return displaySet.needsRerendering; + }); + + const onInteractionHandler = event => { + if (isActive) return; + + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + + viewportGridService.setActiveViewportIndex(viewportIndex); + }; + + // TEMP -> Double click disabled for now + // onDoubleClick={() => onDoubleClick(viewportIndex)} + + viewportPanes[i] = ( + +
+ 1 ? viewportLabel : ''} + dataSource={dataSource} + viewportOptions={viewportOptions} + displaySetOptions={displaySetOptions} + needsRerendering={displaySetsNeedsRerendering} + /> +
+
+ ); + } + + return viewportPanes; + }, [viewports, activeViewportIndex, viewportComponents, dataSource]); + + /** + * Loading indicator until numCols and numRows are gotten from the HangingProtocolService + */ + if (!numRows || !numCols) { + return null; + } + + return ( + + {/* {ViewportPanes} */} + {getViewportPanes()} + + ); +} + +ViewerViewportGrid.propTypes = { + viewportComponents: PropTypes.array.isRequired, +}; + +ViewerViewportGrid.defaultProps = { + viewportComponents: [], +}; + +function _getViewportComponent(displaySets, viewportComponents) { + if (!displaySets || !displaySets.length) { + return EmptyViewport; + } + + // Todo: Do we have a viewport that has two different SOPClassHandlerIds? + const SOPClassHandlerId = displaySets[0].SOPClassHandlerId; + + for (let i = 0; i < viewportComponents.length; i++) { + if ( + viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId) + ) { + const { component } = viewportComponents[i]; + return component; + } + } +} + +export default ViewerViewportGrid; diff --git a/platform/viewer/src/index.js b/platform/viewer/src/index.js index f0ca01053e0..6adab470324 100644 --- a/platform/viewer/src/index.js +++ b/platform/viewer/src/index.js @@ -2,7 +2,7 @@ * Entry point for development and production PWA builds. */ import 'regenerator-runtime/runtime'; -import App from './App.jsx'; +import App from './App.tsx'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -15,19 +15,21 @@ import ReactDOM from 'react-dom'; * pluginImports.js imports all of the modes and extensions and adds them * to the window for processing. */ -import './pluginImports.js'; +import loadDynamicImports from './pluginImports.js'; -/** - * Combine our appConfiguration with installed extensions and modes. - * In the future appConfiguration may contain modes added at runtime. - * */ -const appProps = { - config: window ? window.config : {}, - defaultExtensions: window.extensions, - defaultModes: window.modes, -}; +loadDynamicImports().then(() => { + /** + * Combine our appConfiguration with installed extensions and modes. + * In the future appConfiguration may contain modes added at runtime. + * */ + const appProps = { + config: window ? window.config : {}, + defaultExtensions: window.extensions, + defaultModes: window.modes, + }; -/** Create App */ -const app = React.createElement(App, appProps, null); -/** Render */ -ReactDOM.render(app, document.getElementById('root')); + /** Create App */ + const app = React.createElement(App, appProps, null); + /** Render */ + ReactDOM.render(app, document.getElementById('root')); +}); diff --git a/platform/viewer/src/routes/CallbackPage.js b/platform/viewer/src/routes/CallbackPage.tsx similarity index 85% rename from platform/viewer/src/routes/CallbackPage.js rename to platform/viewer/src/routes/CallbackPage.tsx index 085dbd10a65..74474552fb6 100644 --- a/platform/viewer/src/routes/CallbackPage.js +++ b/platform/viewer/src/routes/CallbackPage.tsx @@ -11,8 +11,7 @@ function CallbackPage({ userManager, onRedirectSuccess }) { .then(user => onRedirectSuccess(user)) .catch(error => onRedirectError(error)); - // todo: add i18n (or return null?) - return
Redirecting...
; + return null; } CallbackPage.propTypes = { diff --git a/platform/viewer/src/routes/DataSourceWrapper.jsx b/platform/viewer/src/routes/DataSourceWrapper.tsx similarity index 95% rename from platform/viewer/src/routes/DataSourceWrapper.jsx rename to platform/viewer/src/routes/DataSourceWrapper.tsx index bc3a679c016..d48908bdc9c 100644 --- a/platform/viewer/src/routes/DataSourceWrapper.jsx +++ b/platform/viewer/src/routes/DataSourceWrapper.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { MODULE_TYPES } from '@ohif/core'; // -import { extensionManager } from '../App.jsx'; +import { extensionManager } from '../App.tsx'; import { useParams, useLocation } from 'react-router'; /** @@ -35,7 +35,7 @@ function DataSourceWrapper(props) { // Grabbing first for now - should get active? const name = webApiDataSources[0].name; // TODO: Why does this return an array? - const dataSource = extensionManager.getDataSources(name)[0] + const dataSource = extensionManager.getDataSources(name)[0]; // Route props --> studies.mapParams // mapParams --> studies.search @@ -86,12 +86,13 @@ function DataSourceWrapper(props) { const newOffset = Math.floor( (queryFilterValues.pageNumber * queryFilterValues.resultsPerPage) / - STUDIES_LIMIT + STUDIES_LIMIT ) * (STUDIES_LIMIT - 1); const isLocationUpdated = data.location !== location; - const isDataInvalid = !isSamePage || !isLoading && - (newOffset !== previousOffset || isLocationUpdated); + const isDataInvalid = + !isSamePage || + (!isLoading && (newOffset !== previousOffset || isLocationUpdated)); if (isDataInvalid) { getData(); diff --git a/platform/viewer/src/routes/Local/Local.jsx b/platform/viewer/src/routes/Local/Local.tsx similarity index 76% rename from platform/viewer/src/routes/Local/Local.jsx rename to platform/viewer/src/routes/Local/Local.tsx index 8a93412c13e..9129c54e59c 100644 --- a/platform/viewer/src/routes/Local/Local.jsx +++ b/platform/viewer/src/routes/Local/Local.tsx @@ -1,14 +1,14 @@ -import React, { useEffect, useRef } from 'react' -import classnames from 'classnames' +import React, { useEffect, useRef } from 'react'; +import classnames from 'classnames'; import { useNavigate } from 'react-router-dom'; -import { MODULE_TYPES } from '@ohif/core' +import { MODULE_TYPES } from '@ohif/core'; -import Dropzone from 'react-dropzone' -import filesToStudies from './filesToStudies' +import Dropzone from 'react-dropzone'; +import filesToStudies from './filesToStudies'; -import { extensionManager } from '../../App.jsx' +import { extensionManager } from '../../App.tsx'; -import { Icon, Button } from '@ohif/ui' +import { Icon, Button } from '@ohif/ui'; const getLoadButton = (onDrop, text, isDir) => { return ( @@ -21,7 +21,7 @@ const getLoadButton = (onDrop, text, isDir) => { disabled={false} endIcon={} // launch-arrow | launch-info className={classnames('font-bold', 'ml-2')} - onClick={() => { }} + onClick={() => {}} > {text} {isDir ? ( @@ -36,41 +36,42 @@ const getLoadButton = (onDrop, text, isDir) => {
)} - ) -} + + ); +}; function Local() { const navigate = useNavigate(); - const dropzoneRef = useRef() + const dropzoneRef = useRef(); // Initializing the dicom local dataSource - const dataSourceModules = extensionManager.modules[MODULE_TYPES.DATA_SOURCE] + const dataSourceModules = extensionManager.modules[MODULE_TYPES.DATA_SOURCE]; const localDataSources = dataSourceModules.reduce((acc, curr) => { - const mods = [] - curr.module.forEach((mod) => { + const mods = []; + curr.module.forEach(mod => { if (mod.type === 'localApi') { - mods.push(mod) + mods.push(mod); } - }) - return acc.concat(mods) - }, []) + }); + return acc.concat(mods); + }, []); - const firstLocalDataSource = localDataSources[0] - const dataSource = firstLocalDataSource.createDataSource({}) + const firstLocalDataSource = localDataSources[0]; + const dataSource = firstLocalDataSource.createDataSource({}); - const onDrop = async (acceptedFiles) => { - const studies = await filesToStudies(acceptedFiles, dataSource) + const onDrop = async acceptedFiles => { + const studies = await filesToStudies(acceptedFiles, dataSource); // Todo: navigate to work list and let user select a mode - navigate(`/viewer/dicomlocal?StudyInstanceUIDs=${studies[0]}`) - } + navigate(`/viewer/dicomlocal?StudyInstanceUIDs=${studies[0]}`); + }; // Set body style useEffect(() => { - document.body.classList.add('bg-black') + document.body.classList.add('bg-black'); return () => { - document.body.classList.remove('bg-black') - } - }, []) + document.body.classList.remove('bg-black'); + }; + }, []); return ( @@ -104,7 +105,7 @@ function Local() {
)} - ) + ); } -export default Local +export default Local; diff --git a/platform/viewer/src/routes/Mode/Compose.js b/platform/viewer/src/routes/Mode/Compose.tsx similarity index 100% rename from platform/viewer/src/routes/Mode/Compose.js rename to platform/viewer/src/routes/Mode/Compose.tsx diff --git a/platform/viewer/src/routes/Mode/Mode.jsx b/platform/viewer/src/routes/Mode/Mode.tsx similarity index 96% rename from platform/viewer/src/routes/Mode/Mode.jsx rename to platform/viewer/src/routes/Mode/Mode.tsx index 15be6a48449..f2e164bbfa3 100644 --- a/platform/viewer/src/routes/Mode/Mode.jsx +++ b/platform/viewer/src/routes/Mode/Mode.tsx @@ -61,6 +61,7 @@ export default function ModeRoute({ dataSourceName, extensionManager, servicesManager, + commandsManager, hotkeysManager, }) { // Parse route params/querystring @@ -197,16 +198,20 @@ export default function ModeRoute({ return; } // TODO: For some reason this is running before the Providers - // are calling setServiceImplementationf - // TOOD -> iterate through services. + // are calling setServiceImplementation + // TODO -> iterate through services. // Extension // Add SOPClassHandlers to a new SOPClassManager. DisplaySetService.init(extensionManager, sopClassHandlers); - extensionManager.onModeEnter(); - mode?.onModeEnter({ servicesManager, extensionManager }); + extensionManager.onModeEnter({ + servicesManager, + extensionManager, + commandsManager, + }); + mode?.onModeEnter({ servicesManager, extensionManager, commandsManager }); // Adding hanging protocols of extensions after onModeEnter since // it will reset the protocols @@ -214,6 +219,7 @@ export default function ModeRoute({ const hangingProtocolModule = extensionManager.getModuleEntry( extensionProtocols ); + if (hangingProtocolModule?.protocols) { HangingProtocolService.addProtocols(hangingProtocolModule.protocols); } @@ -232,8 +238,6 @@ export default function ModeRoute({ return await defaultRouteInit({ servicesManager, - extensionManager, - hotkeysManager, studyInstanceUIDs, dataSource, }); diff --git a/platform/viewer/src/routes/NotFound/NotFound.jsx b/platform/viewer/src/routes/NotFound/NotFound.tsx similarity index 100% rename from platform/viewer/src/routes/NotFound/NotFound.jsx rename to platform/viewer/src/routes/NotFound/NotFound.tsx diff --git a/platform/viewer/src/routes/PrivateRoute.jsx b/platform/viewer/src/routes/PrivateRoute.tsx similarity index 100% rename from platform/viewer/src/routes/PrivateRoute.jsx rename to platform/viewer/src/routes/PrivateRoute.tsx diff --git a/platform/viewer/src/routes/SignoutCallbackComponent.js b/platform/viewer/src/routes/SignoutCallbackComponent.tsx similarity index 92% rename from platform/viewer/src/routes/SignoutCallbackComponent.js rename to platform/viewer/src/routes/SignoutCallbackComponent.tsx index 4af3467d0a1..c24a0e0bdda 100644 --- a/platform/viewer/src/routes/SignoutCallbackComponent.js +++ b/platform/viewer/src/routes/SignoutCallbackComponent.tsx @@ -22,8 +22,7 @@ function SignoutCallbackComponent({ userManager }) { .then(user => onRedirectSuccess(user)) .catch(error => onRedirectError(error)); - // todo: add i18n - return
Redirecting...
; + return null; } SignoutCallbackComponent.propTypes = { diff --git a/platform/viewer/src/routes/WorkList/WorkList.jsx b/platform/viewer/src/routes/WorkList/WorkList.tsx similarity index 99% rename from platform/viewer/src/routes/WorkList/WorkList.jsx rename to platform/viewer/src/routes/WorkList/WorkList.tsx index 5350493c287..14815050eb0 100644 --- a/platform/viewer/src/routes/WorkList/WorkList.jsx +++ b/platform/viewer/src/routes/WorkList/WorkList.tsx @@ -12,8 +12,6 @@ import { useAppConfig } from '@state'; import { useDebounce, useQuery } from '@hooks'; import { utils, hotkeys } from '@ohif/core'; -const { sortBySeriesDate } = utils; - import { Icon, StudyListExpandedRow, @@ -31,6 +29,8 @@ import { import i18n from '@ohif/i18n'; +const { sortBySeriesDate } = utils; + const { availableLanguages, defaultLanguage, currentLanguage } = i18n; const seriesInStudiesMap = new Map(); @@ -309,6 +309,8 @@ function WorkList({ gridCol: 4, }, ], + // Todo: This is actually running for all rows, even if they are + // not clicked on. expandedContent: ( ); @@ -80,7 +83,9 @@ export default function buildModeRoutes({ dataSourceName={defaultDataSourceName} extensionManager={extensionManager} servicesManager={servicesManager} + commandsManager={commandsManager} hotkeysManager={hotkeysManager} + commandsManager={commandsManager} /> ); diff --git a/platform/viewer/src/routes/index.js b/platform/viewer/src/routes/index.tsx similarity index 97% rename from platform/viewer/src/routes/index.js rename to platform/viewer/src/routes/index.tsx index 7820119a8df..2b1570f673a 100644 --- a/platform/viewer/src/routes/index.js +++ b/platform/viewer/src/routes/index.tsx @@ -33,6 +33,7 @@ const createRoutes = ({ dataSources, extensionManager, servicesManager, + commandsManager, hotkeysManager, routerBasename, }) => { @@ -42,6 +43,7 @@ const createRoutes = ({ dataSources, extensionManager, servicesManager, + commandsManager, hotkeysManager, }) || []; @@ -67,7 +69,7 @@ const createRoutes = ({ // Note: PrivateRoutes in react-router-dom 6.x should be defined within // a Route element return ( - + {allRoutes.map((route, i) => { return route.private === true ? ( console.log(stdout); + +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; +if (GITHUB_TOKEN) { + const command = `git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/".insteadOf ssh://git@github.com/`; + console.log(command); + exec(command, log); +} else { + console.log('No GITHUB_TOKEN found, skipping private repo customization'); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000000..04989dbbd6b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "checkJs": true, + // Ensure that Babel can safely transpile files in the TypeScript project + "isolatedModules": false, + "jsx": "react", + "esModuleInterop": true, + "sourceMap": true, + "declaration": true, + "moduleResolution": "node", + "outDir": "./dist", + "paths": { + "@ohif/core": [ + "platform/core/src" + ], + "@ohif/ui": ["platform/ui/src"], + "@ohif/i18n": ["platform/i18n/src"], + "@ohif/viewer": ["platform/viewer/src"] + } + }, + "include": ["platform/**/src/**/*", "extensions/**/src/**/*", "modes/**/src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/yarn.lock b/yarn.lock index 6792c013048..fa9f5bcf836 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,157 +2,144 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz#ec0178e07b44fd74a057728ac157291b26cecf37" - integrity sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A== - dependencies: - "@algolia/autocomplete-shared" "1.5.2" - -"@algolia/autocomplete-preset-algolia@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz#36c5638cc6dba6ea46a86e5a0314637ca40a77ca" - integrity sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw== +"@algolia/autocomplete-core@1.6.3": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.6.3.tgz#76832fffb6405ac2c87bac5a040b8a31a1cdef80" + integrity sha512-dqQqRt01fX3YuVFrkceHsoCnzX0bLhrrg8itJI1NM68KjrPYQPYsE+kY8EZTCM4y8VDnhqJErR73xe/ZsV+qAA== dependencies: - "@algolia/autocomplete-shared" "1.5.2" + "@algolia/autocomplete-shared" "1.6.3" -"@algolia/autocomplete-shared@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz#e157f9ad624ab8fd940ff28bd2094cdf199cdd79" - integrity sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug== +"@algolia/autocomplete-shared@1.6.3": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.6.3.tgz#52085ce89a755977841ed0a463aa31ce8f1dea97" + integrity sha512-UV46bnkTztyADFaETfzFC5ryIdGVb2zpAoYgu0tfcuYWjhg1KbLXveFffZIrGVoboqmAk1b+jMrl6iCja1i3lg== -"@algolia/cache-browser-local-storage@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.13.0.tgz#f8aa4fe31104b19d616ea392f9ed5c2ea847d964" - integrity sha512-nj1vHRZauTqP/bluwkRIgEADEimqojJgoTRCel5f6q8WCa9Y8QeI4bpDQP28FoeKnDRYa3J5CauDlN466jqRhg== +"@algolia/cache-browser-local-storage@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.13.1.tgz#ffacb9230119f77de1a6f163b83680be999110e4" + integrity sha512-UAUVG2PEfwd/FfudsZtYnidJ9eSCpS+LW9cQiesePQLz41NAcddKxBak6eP2GErqyFagSlnVXe/w2E9h2m2ttg== dependencies: - "@algolia/cache-common" "4.13.0" + "@algolia/cache-common" "4.13.1" -"@algolia/cache-common@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.13.0.tgz#27b83fd3939d08d72261b36a07eeafc4cb4d2113" - integrity sha512-f9mdZjskCui/dA/fA/5a+6hZ7xnHaaZI5tM/Rw9X8rRB39SUlF/+o3P47onZ33n/AwkpSbi5QOyhs16wHd55kA== +"@algolia/cache-common@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.13.1.tgz#c933fdec9f73b4f7c69d5751edc92eee4a63d76b" + integrity sha512-7Vaf6IM4L0Jkl3sYXbwK+2beQOgVJ0mKFbz/4qSxKd1iy2Sp77uTAazcX+Dlexekg1fqGUOSO7HS4Sx47ZJmjA== -"@algolia/cache-in-memory@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.13.0.tgz#10801a74550cbabb64b59ff08c56bce9c278ff2d" - integrity sha512-hHdc+ahPiMM92CQMljmObE75laYzNFYLrNOu0Q3/eyvubZZRtY2SUsEEgyUEyzXruNdzrkcDxFYa7YpWBJYHAg== +"@algolia/cache-in-memory@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.13.1.tgz#c19baa67b4597e1a93e987350613ab3b88768832" + integrity sha512-pZzybCDGApfA/nutsFK1P0Sbsq6fYJU3DwIvyKg4pURerlJM4qZbB9bfLRef0FkzfQu7W11E4cVLCIOWmyZeuQ== dependencies: - "@algolia/cache-common" "4.13.0" + "@algolia/cache-common" "4.13.1" -"@algolia/client-account@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.13.0.tgz#f8646dd40d1e9e3353e10abbd5d6c293ea92a8e2" - integrity sha512-FzFqFt9b0g/LKszBDoEsW+dVBuUe1K3scp2Yf7q6pgHWM1WqyqUlARwVpLxqyc+LoyJkTxQftOKjyFUqddnPKA== +"@algolia/client-account@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.13.1.tgz#fea591943665477a23922ab31863ad0732e26c66" + integrity sha512-TFLiZ1KqMiir3FNHU+h3b0MArmyaHG+eT8Iojio6TdpeFcAQ1Aiy+2gb3SZk3+pgRJa/BxGmDkRUwE5E/lv3QQ== dependencies: - "@algolia/client-common" "4.13.0" - "@algolia/client-search" "4.13.0" - "@algolia/transporter" "4.13.0" + "@algolia/client-common" "4.13.1" + "@algolia/client-search" "4.13.1" + "@algolia/transporter" "4.13.1" -"@algolia/client-analytics@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.13.0.tgz#a00bd02df45d71becb9dd4c5c993d805f2e1786d" - integrity sha512-klmnoq2FIiiMHImkzOm+cGxqRLLu9CMHqFhbgSy9wtXZrqb8BBUIUE2VyBe7azzv1wKcxZV2RUyNOMpFqmnRZA== +"@algolia/client-analytics@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.13.1.tgz#5275956b2d0d16997148f2085f1701b6c39ecc32" + integrity sha512-iOS1JBqh7xaL5x00M5zyluZ9+9Uy9GqtYHv/2SMuzNW1qP7/0doz1lbcsP3S7KBbZANJTFHUOfuqyRLPk91iFA== dependencies: - "@algolia/client-common" "4.13.0" - "@algolia/client-search" "4.13.0" - "@algolia/requester-common" "4.13.0" - "@algolia/transporter" "4.13.0" + "@algolia/client-common" "4.13.1" + "@algolia/client-search" "4.13.1" + "@algolia/requester-common" "4.13.1" + "@algolia/transporter" "4.13.1" -"@algolia/client-common@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.13.0.tgz#8bc373d164dbdcce38b4586912bbe162492bcb86" - integrity sha512-GoXfTp0kVcbgfSXOjfrxx+slSipMqGO9WnNWgeMmru5Ra09MDjrcdunsiiuzF0wua6INbIpBQFTC2Mi5lUNqGA== +"@algolia/client-common@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.13.1.tgz#3bf9e3586f20ef85bbb56ccca390f7dbe57c8f4f" + integrity sha512-LcDoUE0Zz3YwfXJL6lJ2OMY2soClbjrrAKB6auYVMNJcoKZZ2cbhQoFR24AYoxnGUYBER/8B+9sTBj5bj/Gqbg== dependencies: - "@algolia/requester-common" "4.13.0" - "@algolia/transporter" "4.13.0" + "@algolia/requester-common" "4.13.1" + "@algolia/transporter" "4.13.1" -"@algolia/client-personalization@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.13.0.tgz#10fb7af356422551f11a67222b39c52306f1512c" - integrity sha512-KneLz2WaehJmNfdr5yt2HQETpLaCYagRdWwIwkTqRVFCv4DxRQ2ChPVW9jeTj4YfAAhfzE6F8hn7wkQ/Jfj6ZA== +"@algolia/client-personalization@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.13.1.tgz#438a1f58576ef19c4ad4addb8417bdacfe2fce2e" + integrity sha512-1CqrOW1ypVrB4Lssh02hP//YxluoIYXAQCpg03L+/RiXJlCs+uIqlzC0ctpQPmxSlTK6h07kr50JQoYH/TIM9w== dependencies: - "@algolia/client-common" "4.13.0" - "@algolia/requester-common" "4.13.0" - "@algolia/transporter" "4.13.0" + "@algolia/client-common" "4.13.1" + "@algolia/requester-common" "4.13.1" + "@algolia/transporter" "4.13.1" -"@algolia/client-search@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.13.0.tgz#2d8ff8e755c4a37ec89968f3f9b358eed005c7f0" - integrity sha512-blgCKYbZh1NgJWzeGf+caKE32mo3j54NprOf0LZVCubQb3Kx37tk1Hc8SDs9bCAE8hUvf3cazMPIg7wscSxspA== +"@algolia/client-search@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.13.1.tgz#5501deed01e23c33d4aaa9f9eb96a849f0fce313" + integrity sha512-YQKYA83MNRz3FgTNM+4eRYbSmHi0WWpo019s5SeYcL3HUan/i5R09VO9dk3evELDFJYciiydSjbsmhBzbpPP2A== dependencies: - "@algolia/client-common" "4.13.0" - "@algolia/requester-common" "4.13.0" - "@algolia/transporter" "4.13.0" + "@algolia/client-common" "4.13.1" + "@algolia/requester-common" "4.13.1" + "@algolia/transporter" "4.13.1" "@algolia/events@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== -"@algolia/logger-common@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.13.0.tgz#be2606e71aae618a1ff1ea9a1b5f5a74284b35a8" - integrity sha512-8yqXk7rMtmQJ9wZiHOt/6d4/JDEg5VCk83gJ39I+X/pwUPzIsbKy9QiK4uJ3aJELKyoIiDT1hpYVt+5ia+94IA== +"@algolia/logger-common@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.13.1.tgz#4221378e701e3f1eacaa051bcd4ba1f25ddfaf4d" + integrity sha512-L6slbL/OyZaAXNtS/1A8SAbOJeEXD5JcZeDCPYDqSTYScfHu+2ePRTDMgUTY4gQ7HsYZ39N1LujOd8WBTmM2Aw== -"@algolia/logger-console@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.13.0.tgz#f28028a760e3d9191e28a10b12925e48f6c9afde" - integrity sha512-YepRg7w2/87L0vSXRfMND6VJ5d6699sFJBRWzZPOlek2p5fLxxK7O0VncYuc/IbVHEgeApvgXx0WgCEa38GVuQ== +"@algolia/logger-console@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.13.1.tgz#423d358e4992dd4bceab0d9a4e99d1fd68107043" + integrity sha512-7jQOTftfeeLlnb3YqF8bNgA2GZht7rdKkJ31OCeSH2/61haO0tWPoNRjZq9XLlgMQZH276pPo0NdiArcYPHjCA== dependencies: - "@algolia/logger-common" "4.13.0" + "@algolia/logger-common" "4.13.1" -"@algolia/requester-browser-xhr@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.13.0.tgz#e2483f4e8d7f09e27cd0daf6c77711d15c5a919f" - integrity sha512-Dj+bnoWR5MotrnjblzGKZ2kCdQi2cK/VzPURPnE616NU/il7Ypy6U6DLGZ/ZYz+tnwPa0yypNf21uqt84fOgrg== +"@algolia/requester-browser-xhr@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.13.1.tgz#f8ea79233cf6f0392feaf31e35a6b40d68c5bc9e" + integrity sha512-oa0CKr1iH6Nc7CmU6RE7TnXMjHnlyp7S80pP/LvZVABeJHX3p/BcSCKovNYWWltgTxUg0U1o+2uuy8BpMKljwA== dependencies: - "@algolia/requester-common" "4.13.0" + "@algolia/requester-common" "4.13.1" -"@algolia/requester-common@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.13.0.tgz#47fb3464cfb26b55ba43676d13f295d812830596" - integrity sha512-BRTDj53ecK+gn7ugukDWOOcBRul59C4NblCHqj4Zm5msd5UnHFjd/sGX+RLOEoFMhetILAnmg6wMrRrQVac9vw== +"@algolia/requester-common@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.13.1.tgz#daea143d15ab6ed3909c4c45877f1b6c36a16179" + integrity sha512-eGVf0ID84apfFEuXsaoSgIxbU3oFsIbz4XiotU3VS8qGCJAaLVUC5BUJEkiFENZIhon7hIB4d0RI13HY4RSA+w== -"@algolia/requester-node-http@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.13.0.tgz#7d981bbd31492f51dd11820a665f9d8906793c37" - integrity sha512-9b+3O4QFU4azLhGMrZAr/uZPydvzOR4aEZfSL8ZrpLZ7fbbqTO0S/5EVko+QIgglRAtVwxvf8UJ1wzTD2jvKxQ== +"@algolia/requester-node-http@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.13.1.tgz#32c63d4c009f22d97e396406de7af9b66fb8e89d" + integrity sha512-7C0skwtLdCz5heKTVe/vjvrqgL/eJxmiEjHqXdtypcE5GCQCYI15cb+wC4ytYioZDMiuDGeVYmCYImPoEgUGPw== dependencies: - "@algolia/requester-common" "4.13.0" + "@algolia/requester-common" "4.13.1" -"@algolia/transporter@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.13.0.tgz#f6379e5329efa2127da68c914d1141f5f21dbd07" - integrity sha512-8tSQYE+ykQENAdeZdofvtkOr5uJ9VcQSWgRhQ9h01AehtBIPAczk/b2CLrMsw5yQZziLs5cZ3pJ3478yI+urhA== +"@algolia/transporter@4.13.1": + version "4.13.1" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.13.1.tgz#509e03e9145102843d5be4a031c521f692d4e8d6" + integrity sha512-pICnNQN7TtrcYJqqPEXByV8rJ8ZRU2hCiIKLTLRyNpghtQG3VAFk6fVtdzlNfdUGZcehSKGarPIZEHlQXnKjgw== dependencies: - "@algolia/cache-common" "4.13.0" - "@algolia/logger-common" "4.13.0" - "@algolia/requester-common" "4.13.0" + "@algolia/cache-common" "4.13.1" + "@algolia/logger-common" "4.13.1" + "@algolia/requester-common" "4.13.1" "@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" - integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" "@apideck/better-ajv-errors@^0.3.1": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.3.tgz#ab0b1e981e1749bf59736cf7ebe25cfc9f949c15" - integrity sha512-9o+HO2MbJhJHjDYZaDxJmSDckvDpiuItEsrIShV0DXeCshXWRHhqYyU/PKHMkuClOmFnZhRd6wzv4vpDu/dRKg== + version "0.3.4" + resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.4.tgz#f89924dd4efd04a51835db7eb549a7177e0ca727" + integrity sha512-Ic2d8ZT6HJiSikGVQvSklaFyw1OUv4g8sDOxa0PXSlbmN/3gL5IO1WYY9DOwTDqOFmjWoqG1yaaKnPDqYCE9KA== dependencies: json-schema "^0.4.0" jsonpointer "^5.0.0" leven "^3.1.0" -"@babel/code-frame@7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" - integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== - dependencies: - "@babel/highlight" "^7.10.4" - "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -160,17 +147,17 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: "@babel/highlight" "^7.16.7" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" - integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.10": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" + integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== "@babel/core@7.12.9": version "7.12.9" @@ -194,35 +181,35 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.10", "@babel/core@^7.12.16", "@babel/core@^7.12.3", "@babel/core@^7.13.10", "@babel/core@^7.14.8", "@babel/core@^7.17.8", "@babel/core@^7.7.5": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" - integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== +"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.13.10", "@babel/core@^7.14.8", "@babel/core@^7.15.5", "@babel/core@^7.17.10", "@babel/core@^7.17.8", "@babel/core@^7.7.5": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876" + integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.9" - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helpers" "^7.17.9" - "@babel/parser" "^7.17.9" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.0" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.12.15", "@babel/generator@^7.12.5", "@babel/generator@^7.17.9", "@babel/generator@^7.4.0": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" - integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.17.10", "@babel/generator@^7.18.2", "@babel/generator@^7.4.0": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" + integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== dependencies: - "@babel/types" "^7.17.0" + "@babel/types" "^7.18.2" + "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" - source-map "^0.5.0" "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" @@ -239,20 +226,20 @@ "@babel/helper-explode-assignable-expression" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" - integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.10", "@babel/helper-compilation-targets@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== dependencies: - "@babel/compat-data" "^7.17.7" + "@babel/compat-data" "^7.17.10" "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.17.5" + browserslist "^4.20.2" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.6", "@babel/helper-create-class-features-plugin@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz#71835d7fb9f38bd9f1378e40a4c0902fdc2ea49d" - integrity sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ== +"@babel/helper-create-class-features-plugin@^7.17.12", "@babel/helper-create-class-features-plugin@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.0.tgz#fac430912606331cb075ea8d82f9a4c145a4da19" + integrity sha512-Kh8zTGR9de3J63e5nS0rQUdRs/kbtwoeQQ0sriS0lItjC96u8XXZN6lKpuyWd2coKSU13py/y+LTmThLuVX0Pg== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-environment-visitor" "^7.16.7" @@ -262,10 +249,10 @@ "@babel/helper-replace-supers" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" -"@babel/helper-create-regexp-features-plugin@^7.16.7": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" - integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== +"@babel/helper-create-regexp-features-plugin@^7.16.7", "@babel/helper-create-regexp-features-plugin@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz#bb37ca467f9694bbe55b884ae7a5cc1e0084e4fd" + integrity sha512-b2aZrV4zvutr9AIa6/gA3wsZKRwTKYoDxYiFKcESS3Ug2GTXzwBEvMuuFLhCQpEnRXs1zng4ISAXSUxxKBIcxw== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" regexpu-core "^5.0.1" @@ -298,12 +285,10 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" - integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== - dependencies: - "@babel/types" "^7.16.7" +"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" + integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== "@babel/helper-explode-assignable-expression@^7.16.7": version "7.16.7" @@ -327,7 +312,7 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.16.7", "@babel/helper-member-expression-to-functions@^7.17.7": +"@babel/helper-member-expression-to-functions@^7.17.7": version "7.17.7" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== @@ -341,10 +326,10 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" - integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== dependencies: "@babel/helper-environment-visitor" "^7.16.7" "@babel/helper-module-imports" "^7.16.7" @@ -352,8 +337,8 @@ "@babel/helper-split-export-declaration" "^7.16.7" "@babel/helper-validator-identifier" "^7.16.7" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.18.0" + "@babel/types" "^7.18.0" "@babel/helper-optimise-call-expression@^7.16.7": version "7.16.7" @@ -367,10 +352,10 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" - integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== "@babel/helper-remap-async-to-generator@^7.16.8": version "7.16.8" @@ -381,23 +366,23 @@ "@babel/helper-wrap-function" "^7.16.8" "@babel/types" "^7.16.8" -"@babel/helper-replace-supers@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" - integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== +"@babel/helper-replace-supers@^7.16.7", "@babel/helper-replace-supers@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz#41fdfcc9abaf900e18ba6e5931816d9062a7b2e0" + integrity sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q== dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-member-expression-to-functions" "^7.17.7" "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/traverse" "^7.16.7" - "@babel/types" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" -"@babel/helper-simple-access@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" - integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== +"@babel/helper-simple-access@^7.17.7", "@babel/helper-simple-access@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== dependencies: - "@babel/types" "^7.17.0" + "@babel/types" "^7.18.2" "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" @@ -433,81 +418,81 @@ "@babel/traverse" "^7.16.8" "@babel/types" "^7.16.8" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.17.9": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" - integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" + integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== dependencies: "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" - integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" + integrity sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg== dependencies: "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.16", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.9", "@babel/parser@^7.4.3": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" - integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.10", "@babel/parser@^7.18.0", "@babel/parser@^7.4.3": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef" + integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" - integrity sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.17.12.tgz#1dca338caaefca368639c9ffb095afbd4d420b1e" + integrity sha512-xCJQXl4EeQ3J9C4yOmpTrtVGmzpm2iSzyxbkZHw7UCnZBftHpF/hpII80uWVyVrc40ytIClHjgWGTG1g/yB+aw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" - integrity sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.17.12.tgz#0d498ec8f0374b1e2eb54b9cb2c4c78714c77753" + integrity sha512-/vt0hpIw0x4b6BLKUkwlvEoiGZYYLNZ96CzyHYPbtG2jZGz6LBe7/V+drYrc/d+ovrF9NBi0pmtvmNb/FsWtRQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-proposal-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-optional-chaining" "^7.17.12" -"@babel/plugin-proposal-async-generator-functions@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz#3bdd1ebbe620804ea9416706cd67d60787504bc8" - integrity sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ== +"@babel/plugin-proposal-async-generator-functions@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.17.12.tgz#094a417e31ce7e692d84bab06c8e2a607cbeef03" + integrity sha512-RWVvqD1ooLKP6IqWTA5GyFVX2isGEgC5iFxKzfYOIy/QEFdxYyCybBDtIGjipHpb9bDWHzcqGqFakf+mVmBTdQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" - integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== +"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.16.7", "@babel/plugin-proposal-class-properties@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.17.12.tgz#84f65c0cc247d46f40a6da99aadd6438315d80a4" + integrity sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-proposal-class-static-block@^7.16.7": - version "7.17.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz#164e8fd25f0d80fa48c5a4d1438a6629325ad83c" - integrity sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA== +"@babel/plugin-proposal-class-static-block@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.0.tgz#7d02253156e3c3793bdb9f2faac3a1c05f0ba710" + integrity sha512-t+8LsRMMDE74c6sV7KShIw13sqbqd58tlqNrsWoWBTIMw7SVQ0cZ905wLNS/FBCy/3PyooRHLFFlfrUNyyz5lA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.6" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-proposal-decorators@^7.12.12": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.17.9.tgz#67a1653be9c77ce5b6c318aa90c8287b87831619" - integrity sha512-EfH2LZ/vPa2wuPwJ26j+kYRkaubf89UlwxKXtxqEm57HrgSEYDB8t4swFP+p8LcI9yiP9ZRJJjo/58hS6BnaDA== + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.2.tgz#dbe4086d2d42db489399783c3aa9272e9700afd4" + integrity sha512-kbDISufFOxeczi0v4NQP3p5kIeW6izn/6klfWBrIIdGZZe4UpHR+QU03FAoWjGGd9SUXAwbw2pup1kaL4OQsJQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.9" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-replace-supers" "^7.18.2" "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/plugin-syntax-decorators" "^7.17.0" + "@babel/plugin-syntax-decorators" "^7.17.12" charcodes "^0.2.0" "@babel/plugin-proposal-dynamic-import@^7.16.7": @@ -519,43 +504,43 @@ "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-proposal-export-default-from@^7.12.1": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.16.7.tgz#a40ab158ca55627b71c5513f03d3469026a9e929" - integrity sha512-+cENpW1rgIjExn+o5c8Jw/4BuH4eGKKYvkMB8/0ZxFQ9mC0t4z09VsPIwNg6waF69QYC81zxGeAsREGuqQoKeg== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.17.12.tgz#df785e638618d8ffa14e08c78c44d9695d083b73" + integrity sha512-LpsTRw725eBAXXKUOnJJct+SEaOzwR78zahcLuripD2+dKc2Sj+8Q2DzA+GC/jOpOu/KlDXuxrzG214o1zTauQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-export-default-from" "^7.16.7" -"@babel/plugin-proposal-export-namespace-from@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz#09de09df18445a5786a305681423ae63507a6163" - integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== +"@babel/plugin-proposal-export-namespace-from@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.17.12.tgz#b22864ccd662db9606edb2287ea5fd1709f05378" + integrity sha512-j7Ye5EWdwoXOpRmo5QmRyHPsDIe6+u70ZYZrd7uz+ebPYFKfRcLcNu3Ro0vOlJ5zuv8rU7xa+GttNiRzX56snQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz#9732cb1d17d9a2626a08c5be25186c195b6fa6e8" - integrity sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ== +"@babel/plugin-proposal-json-strings@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.17.12.tgz#f4642951792437233216d8c1af370bb0fbff4664" + integrity sha512-rKJ+rKBoXwLnIn7n6o6fulViHMrOThz99ybH+hKHcOZbnN14VuMnH9fo2eHE69C8pO4uX1Q7t2HYYIDmv8VYkg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz#be23c0ba74deec1922e639832904be0bea73cdea" - integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== +"@babel/plugin-proposal-logical-assignment-operators@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.17.12.tgz#c64a1bcb2b0a6d0ed2ff674fd120f90ee4b88a23" + integrity sha512-EqFo2s1Z5yy+JeJu7SFfbIUtToJTVlC61/C7WLKDntSw4Sz6JNAIfL7zQ74VvirxpjB5kz/kIx0gCcb+5OEo2Q== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1", "@babel/plugin-proposal-nullish-coalescing-operator@^7.12.13", "@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" - integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1", "@babel/plugin-proposal-nullish-coalescing-operator@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.17.12.tgz#1e93079bbc2cbc756f6db6a1925157c4a92b94be" + integrity sha512-ws/g3FSGVzv+VH86+QvgtuJL/kR67xaEIF2x0iPqdDfYW6ra6JF3lKVBkWynRLcNtIC1oCTfDRVxmm2mKzy+ag== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" "@babel/plugin-proposal-numeric-separator@^7.16.7": @@ -575,16 +560,16 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.16.7", "@babel/plugin-proposal-object-rest-spread@^7.17.3": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz#d9eb649a54628a51701aef7e0ea3d17e2b9dd390" - integrity sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw== +"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.17.3", "@babel/plugin-proposal-object-rest-spread@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.0.tgz#79f2390c892ba2a68ec112eb0d895cfbd11155e8" + integrity sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw== dependencies: - "@babel/compat-data" "^7.17.0" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/compat-data" "^7.17.10" + "@babel/helper-compilation-targets" "^7.17.10" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.17.12" "@babel/plugin-proposal-optional-catch-binding@^7.16.7": version "7.16.7" @@ -594,40 +579,40 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.12.16", "@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" - integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== +"@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.17.12.tgz#f96949e9bacace3a9066323a5cf90cfb9de67174" + integrity sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@^7.12.1", "@babel/plugin-proposal-private-methods@^7.16.11": - version "7.16.11" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz#e8df108288555ff259f4527dbe84813aac3a1c50" - integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== +"@babel/plugin-proposal-private-methods@^7.12.1", "@babel/plugin-proposal-private-methods@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.17.12.tgz#c2ca3a80beb7539289938da005ad525a038a819c" + integrity sha512-SllXoxo19HmxhDWm3luPz+cPhtoTSKLJE9PXshsfrOzBqs60QP0r8OaJItrPhAj0d7mZMnNF0Y1UUggCDgMz1A== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.10" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-proposal-private-property-in-object@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" - integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== +"@babel/plugin-proposal-private-property-in-object@^7.12.1", "@babel/plugin-proposal-private-property-in-object@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.17.12.tgz#b02efb7f106d544667d91ae97405a9fd8c93952d" + integrity sha512-/6BtVi57CJfrtDNKfK5b66ydK2J5pXUKBKSPD2G1whamMuEnZWgoOIfO8Vf9F/DoD4izBLD/Au4NMQfruzzykg== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-create-class-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2" - integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg== +"@babel/plugin-proposal-unicode-property-regex@^7.17.12", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.17.12.tgz#3dbd7a67bd7f94c8238b394da112d86aaf32ad4d" + integrity sha512-Wb9qLjXf3ZazqXA7IvI7ozqRIXIGPtSo+L5coFmEkhTQK18ao4UDDD0zdTGAarmbLj2urpRwrc6893cu5Bfh0A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-create-regexp-features-plugin" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -650,12 +635,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-decorators@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.0.tgz#a2be3b2c9fe7d78bd4994e790896bc411e2f166d" - integrity sha512-qWe85yCXsvDEluNP0OyeQjH63DlhAR3W7K9BxxU1MvbDb48tgBG+Ao6IJJ6smPDrrVzSQZrbF6donpkFBMcs3A== +"@babel/plugin-syntax-decorators@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.17.12.tgz#02e8f678602f0af8222235271efea945cfdb018a" + integrity sha512-D1Hz0qtGTza8K2xGyEdVNCYLdVHukAcbQr4K3/s6r/esadyEriZovpJimQOpu8ju4/jV8dW/1xdaE0UpDroidw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" @@ -678,12 +663,19 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-flow@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.16.7.tgz#202b147e5892b8452bbb0bb269c7ed2539ab8832" - integrity sha512-UDo3YGQO0jH6ytzVwgSLv9i/CzMcUjbKenL67dTrAZPPv6GFAtDhe6jqnvmoKzC/7htNTohhos+onPtDMqJwaQ== +"@babel/plugin-syntax-flow@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.17.12.tgz#23d852902acd19f42923fca9d0f196984d124e73" + integrity sha512-B8QIgBvkIG6G2jgsOHQUist7Sm0EBLDCx8sen072IwqNuzMegZNXrYnSv77cYzA8mLDZAfQYqsLIhimiP1s2HQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" + +"@babel/plugin-syntax-import-assertions@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.17.12.tgz#58096a92b11b2e4e54b24c6a0cc0e5e607abcedd" + integrity sha512-n/loy2zkq9ZEM8tEOwON9wTQSTNDTDEz6NujPtJGLU7qObzT1N4c4YZZf8E6ATB2AjNQg/Ib2AIpO03EZaCehw== + dependencies: + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" @@ -699,12 +691,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-jsx@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" - integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== +"@babel/plugin-syntax-jsx@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz#834035b45061983a491f60096f61a2e7c5674a47" + integrity sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -762,27 +754,27 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" - integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== +"@babel/plugin-syntax-typescript@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" + integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-arrow-functions@^7.12.1", "@babel/plugin-transform-arrow-functions@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" - integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== +"@babel/plugin-transform-arrow-functions@^7.12.1", "@babel/plugin-transform-arrow-functions@^7.16.7", "@babel/plugin-transform-arrow-functions@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.17.12.tgz#dddd783b473b1b1537ef46423e3944ff24898c45" + integrity sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-async-to-generator@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz#b83dff4b970cf41f1b819f8b49cc0cfbaa53a808" - integrity sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg== +"@babel/plugin-transform-async-to-generator@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.17.12.tgz#dbe5511e6b01eee1496c944e35cdfe3f58050832" + integrity sha512-J8dbrWIOO3orDzir57NRsjg4uxucvhby0L/KZuGsWDj0g7twWK3g7JhJhOrXtuXiw8MeiSdJ3E0OW9H8LYEzLQ== dependencies: "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-transform-block-scoped-functions@^7.16.7": @@ -792,40 +784,40 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" - integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== +"@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.17.12": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz#7988627b3e9186a13e4d7735dc9c34a056613fb9" + integrity sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" - integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== +"@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.17.12": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz#51310b812a090b846c784e47087fa6457baef814" + integrity sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" + "@babel/helper-environment-visitor" "^7.18.2" + "@babel/helper-function-name" "^7.17.9" "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-replace-supers" "^7.18.2" "@babel/helper-split-export-declaration" "^7.16.7" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" - integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== +"@babel/plugin-transform-computed-properties@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.17.12.tgz#bca616a83679698f3258e892ed422546e531387f" + integrity sha512-a7XINeplB5cQUWMg1E/GI1tFz3LfK021IjV1rj1ypE+R7jHm+pIHmHl25VNkZxtx9uuYp7ThGk8fur1HHG7PgQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.16.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz#49dc2675a7afa9a5e4c6bdee636061136c3408d1" - integrity sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ== +"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.0.tgz#dc4f92587e291b4daa78aa20cc2d7a63aa11e858" + integrity sha512-Mo69klS79z6KEfrLg/1WkmVnB8javh75HX4pi2btjvlIoasuxilEyjtsQW6XPrubNd7AQy0MMaNIaQE4e7+PQw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.16.7" @@ -835,12 +827,12 @@ "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-duplicate-keys@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz#2207e9ca8f82a0d36a5a67b6536e7ef8b08823c9" - integrity sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw== +"@babel/plugin-transform-duplicate-keys@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.17.12.tgz#a09aa709a3310013f8e48e0e23bc7ace0f21477c" + integrity sha512-EA5eYFUG6xeerdabina/xIoB95jJ17mAkR8ivx6ZSu9frKShBjpOGZPn511MTDTkiCO+zXnzNczvUM69YSf3Zw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-exponentiation-operator@^7.16.7": version "7.16.7" @@ -850,20 +842,20 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-flow-strip-types@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.16.7.tgz#291fb140c78dabbf87f2427e7c7c332b126964b8" - integrity sha512-mzmCq3cNsDpZZu9FADYYyfZJIOrSONmHcop2XEKPdBNMa4PDC4eEvcOvzZaCNcjKu72v0XQlA5y1g58aLRXdYg== +"@babel/plugin-transform-flow-strip-types@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.17.12.tgz#5e070f99a4152194bd9275de140e83a92966cab3" + integrity sha512-g8cSNt+cHCpG/uunPQELdq/TeV3eg1OLJYwxypwHtAWo9+nErH3lQx9CSO2uI9lF74A0mR0t4KoMjs1snSgnTw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-flow" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/plugin-syntax-flow" "^7.17.12" -"@babel/plugin-transform-for-of@^7.12.1", "@babel/plugin-transform-for-of@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" - integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== +"@babel/plugin-transform-for-of@^7.12.1", "@babel/plugin-transform-for-of@^7.18.1": + version "7.18.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.1.tgz#ed14b657e162b72afbbb2b4cdad277bf2bb32036" + integrity sha512-+TTB5XwvJ5hZbO8xvl2H4XaMDOAK57zF4miuC9qQJgysPNEAZZ9Z69rdF5LJkozGdZrjBIUAIyKUWRMmebI7vg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-function-name@^7.16.7": version "7.16.7" @@ -874,12 +866,12 @@ "@babel/helper-function-name" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" - integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== +"@babel/plugin-transform-literals@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.17.12.tgz#97131fbc6bbb261487105b4b3edbf9ebf9c830ae" + integrity sha512-8iRkvaTjJciWycPIZ9k9duu663FT7VrBdNqNgxnVXEFwOIp55JWcZd23VBRySYbnS3PwQ3rGiabJBBBGj5APmQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-member-expression-literals@^7.16.7": version "7.16.7" @@ -888,57 +880,58 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-modules-amd@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz#b28d323016a7daaae8609781d1f8c9da42b13186" - integrity sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g== +"@babel/plugin-transform-modules-amd@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.0.tgz#7ef1002e67e36da3155edc8bf1ac9398064c02ed" + integrity sha512-h8FjOlYmdZwl7Xm2Ug4iX2j7Qy63NANI+NQVWQzv6r25fqgg7k2dZl03p95kvqNclglHs4FZ+isv4p1uXMA+QA== dependencies: - "@babel/helper-module-transforms" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.16.8": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.9.tgz#274be1a2087beec0254d4abd4d86e52442e1e5b6" - integrity sha512-2TBFd/r2I6VlYn0YRTz2JdazS+FoUuQ2rIFHoAxtyP/0G3D82SBLaRq9rnUkpqlLg03Byfl/+M32mpxjO6KaPw== +"@babel/plugin-transform-modules-commonjs@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz#1aa8efa2e2a6e818b6a7f2235fceaf09bdb31e9e" + integrity sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ== dependencies: - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/helper-simple-access" "^7.18.2" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.16.7": - version "7.17.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz#81fd834024fae14ea78fbe34168b042f38703859" - integrity sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw== +"@babel/plugin-transform-modules-systemjs@^7.18.0": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.4.tgz#3d6fd9868c735cce8f38d6ae3a407fb7e61e6d46" + integrity sha512-lH2UaQaHVOAeYrUUuZ8i38o76J/FnO8vu21OE+tD1MyP9lxdZoSfz+pDbWkq46GogUrdrMz3tiz/FYGB+bVThg== dependencies: "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-identifier" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz#23dad479fa585283dbd22215bff12719171e7618" - integrity sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ== +"@babel/plugin-transform-modules-umd@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.0.tgz#56aac64a2c2a1922341129a4597d1fd5c3ff020f" + integrity sha512-d/zZ8I3BWli1tmROLxXLc9A6YXvGK8egMxHp+E/rRwMh1Kip0AP77VwZae3snEJ33iiWwvNv2+UIIhfalqhzZA== dependencies: - "@babel/helper-module-transforms" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz#7f860e0e40d844a02c9dcf9d84965e7dfd666252" - integrity sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.17.12.tgz#9c4a5a5966e0434d515f2675c227fd8cc8606931" + integrity sha512-vWoWFM5CKaTeHrdUJ/3SIOTRV+MBVGybOC9mhJkaprGNt5demMymDW24yC74avb915/mIRe3TgNb/d8idvnCRA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-create-regexp-features-plugin" "^7.17.12" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-new-target@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz#9967d89a5c243818e0800fdad89db22c5f514244" - integrity sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg== +"@babel/plugin-transform-new-target@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.17.12.tgz#10842cd605a620944e81ea6060e9e65c265742e3" + integrity sha512-CaOtzk2fDYisbjAD4Sd1MTKGVIpRtx9bWLyj24Y/k6p4s4gQ3CqDGJauFJxt8M/LEx003d0i3klVqnN73qvK3w== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-object-super@^7.16.7": version "7.16.7" @@ -948,12 +941,12 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-replace-supers" "^7.16.7" -"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" - integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== +"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.17.12.tgz#eb467cd9586ff5ff115a9880d6fdbd4a846b7766" + integrity sha512-6qW4rWo1cyCdq1FkYri7AHpauchbGLXpdwnYsfxFb+KtddHENfsY5JZb35xUwkK5opOLcJ3BNd2l7PhRYGlwIA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-property-literals@^7.16.7": version "7.16.7" @@ -962,12 +955,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-react-constant-elements@^7.12.1": - version "7.17.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.17.6.tgz#6cc273c2f612a6a50cb657e63ee1303e5e68d10a" - integrity sha512-OBv9VkyyKtsHZiHLoSfCn+h6yU7YKX8nrs32xUmOa1SRSk+t03FosB6fBZ0Yz4BpD1WV7l73Nsad+2Tz7APpqw== +"@babel/plugin-transform-react-constant-elements@^7.12.1", "@babel/plugin-transform-react-constant-elements@^7.14.5": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.17.12.tgz#cc580857696b6dd9e5e3d079e673d060a0657f37" + integrity sha512-maEkX2xs2STuv2Px8QuqxqjhV2LsFobT1elCgyU5704fcyTu9DyD/bJXxD/mrRiVyhpHweOQ00OJ5FKhHq9oEw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/plugin-transform-react-display-name@^7.16.7": version "7.16.7" @@ -983,46 +976,47 @@ dependencies: "@babel/plugin-transform-react-jsx" "^7.16.7" -"@babel/plugin-transform-react-jsx@^7.12.12", "@babel/plugin-transform-react-jsx@^7.16.7": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" - integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== +"@babel/plugin-transform-react-jsx@^7.12.12", "@babel/plugin-transform-react-jsx@^7.16.7", "@babel/plugin-transform-react-jsx@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.12.tgz#2aa20022709cd6a3f40b45d60603d5f269586dba" + integrity sha512-Lcaw8bxd1DKht3thfD4A12dqo1X16he1Lm8rIv8sTwjAYNInRS1qHa9aJoqvzpscItXvftKDCfaEQzwoVyXpEQ== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-jsx" "^7.16.7" - "@babel/types" "^7.17.0" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/plugin-syntax-jsx" "^7.17.12" + "@babel/types" "^7.17.12" "@babel/plugin-transform-react-pure-annotations@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz#232bfd2f12eb551d6d7d01d13fe3f86b45eb9c67" - integrity sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA== + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.0.tgz#ef82c8e310913f3522462c9ac967d395092f1954" + integrity sha512-6+0IK6ouvqDn9bmEG7mEyF/pwlJXVj5lwydybpyyH3D0A7Hftk+NCTdYjnLNZksn261xaOV5ksmp20pQEmc2RQ== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-regenerator@^7.16.7": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.17.9.tgz#0a33c3a61cf47f45ed3232903683a0afd2d3460c" - integrity sha512-Lc2TfbxR1HOyn/c6b4Y/b6NHoTb67n/IoWLxTu4kC7h4KQnWlhCq2S8Tx0t2SVvv5Uu87Hs+6JEJ5kt2tYGylQ== +"@babel/plugin-transform-regenerator@^7.16.7", "@babel/plugin-transform-regenerator@^7.18.0": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz#44274d655eb3f1af3f3a574ba819d3f48caf99d5" + integrity sha512-C8YdRw9uzx25HSIzwA7EM7YP0FhCe5wNvJbZzjVNHHPGVcDJ3Aie+qGYYdS1oVQgn+B3eAIJbWFLrJ4Jipv7nw== dependencies: + "@babel/helper-plugin-utils" "^7.17.12" regenerator-transform "^0.15.0" -"@babel/plugin-transform-reserved-words@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz#1d798e078f7c5958eec952059c460b220a63f586" - integrity sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg== +"@babel/plugin-transform-reserved-words@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.17.12.tgz#7dbd349f3cdffba751e817cf40ca1386732f652f" + integrity sha512-1KYqwbJV3Co03NIi14uEHW8P50Md6KqFgt0FfpHdK6oyAHQVTosgPuPSiWud1HX0oYJ1hGRRlk0fP87jFpqXZA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-runtime@^7.12.15", "@babel/plugin-transform-runtime@^7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" - integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== +"@babel/plugin-transform-runtime@^7.17.0", "@babel/plugin-transform-runtime@^7.17.10": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.2.tgz#04637de1e45ae8847ff14b9beead09c33d34374d" + integrity sha512-mr1ufuRMfS52ttq+1G1PD8OJNqgcTFjq3hwn8SZ5n1x1pBhi0E36rYMdTK0TsKtApJ4lDEdfXJwtGobQMHSMPg== dependencies: "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" babel-plugin-polyfill-corejs2 "^0.3.0" babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" @@ -1035,12 +1029,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-spread@^7.12.1", "@babel/plugin-transform-spread@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" - integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== +"@babel/plugin-transform-spread@^7.12.1", "@babel/plugin-transform-spread@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.17.12.tgz#c112cad3064299f03ea32afed1d659223935d1f5" + integrity sha512-9pgmuQAtFi3lpNUstvG9nGfk9DkrdmWNp9KeKPFmuZCpEnxRzYlS8JgwPjYj+1AWDOSvoGN0H30p1cBOmT/Svg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-transform-sticky-regex@^7.16.7": @@ -1050,28 +1044,28 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-template-literals@^7.12.1", "@babel/plugin-transform-template-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" - integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== +"@babel/plugin-transform-template-literals@^7.12.1", "@babel/plugin-transform-template-literals@^7.18.2": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz#31ed6915721864847c48b656281d0098ea1add28" + integrity sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-typeof-symbol@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz#9cdbe622582c21368bd482b660ba87d5545d4f7e" - integrity sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ== +"@babel/plugin-transform-typeof-symbol@^7.17.12": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.17.12.tgz#0f12f57ac35e98b35b4ed34829948d42bd0e6889" + integrity sha512-Q8y+Jp7ZdtSPXCThB6zjQ74N3lj0f6TDh1Hnf5B+sYlzQ8i5Pjp8gW0My79iekSpT4WnI06blqP6DT0OmaXXmw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-typescript@^7.16.7": - version "7.16.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" - integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== +"@babel/plugin-transform-typescript@^7.13.0", "@babel/plugin-transform-typescript@^7.17.12": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz#587eaf6a39edb8c06215e550dc939faeadd750bf" + integrity sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-typescript" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.18.0" + "@babel/helper-plugin-utils" "^7.17.12" + "@babel/plugin-syntax-typescript" "^7.17.12" "@babel/plugin-transform-unicode-escapes@^7.16.7": version "7.16.7" @@ -1088,45 +1082,38 @@ "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" -"@babel/polyfill@^7.8.3": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96" - integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g== - dependencies: - core-js "^2.6.5" - regenerator-runtime "^0.13.4" - -"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.12.11", "@babel/preset-env@^7.12.16", "@babel/preset-env@^7.16.11": - version "7.16.11" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.11.tgz#5dd88fd885fae36f88fd7c8342475c9f0abe2982" - integrity sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g== +"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.12.11", "@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.11", "@babel/preset-env@^7.17.10": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.2.tgz#f47d3000a098617926e674c945d95a28cb90977a" + integrity sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q== dependencies: - "@babel/compat-data" "^7.16.8" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/compat-data" "^7.17.10" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.7" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.7" - "@babel/plugin-proposal-async-generator-functions" "^7.16.8" - "@babel/plugin-proposal-class-properties" "^7.16.7" - "@babel/plugin-proposal-class-static-block" "^7.16.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.17.12" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.17.12" + "@babel/plugin-proposal-async-generator-functions" "^7.17.12" + "@babel/plugin-proposal-class-properties" "^7.17.12" + "@babel/plugin-proposal-class-static-block" "^7.18.0" "@babel/plugin-proposal-dynamic-import" "^7.16.7" - "@babel/plugin-proposal-export-namespace-from" "^7.16.7" - "@babel/plugin-proposal-json-strings" "^7.16.7" - "@babel/plugin-proposal-logical-assignment-operators" "^7.16.7" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.7" + "@babel/plugin-proposal-export-namespace-from" "^7.17.12" + "@babel/plugin-proposal-json-strings" "^7.17.12" + "@babel/plugin-proposal-logical-assignment-operators" "^7.17.12" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.17.12" "@babel/plugin-proposal-numeric-separator" "^7.16.7" - "@babel/plugin-proposal-object-rest-spread" "^7.16.7" + "@babel/plugin-proposal-object-rest-spread" "^7.18.0" "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" - "@babel/plugin-proposal-optional-chaining" "^7.16.7" - "@babel/plugin-proposal-private-methods" "^7.16.11" - "@babel/plugin-proposal-private-property-in-object" "^7.16.7" - "@babel/plugin-proposal-unicode-property-regex" "^7.16.7" + "@babel/plugin-proposal-optional-chaining" "^7.17.12" + "@babel/plugin-proposal-private-methods" "^7.17.12" + "@babel/plugin-proposal-private-property-in-object" "^7.17.12" + "@babel/plugin-proposal-unicode-property-regex" "^7.17.12" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.17.12" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -1136,54 +1123,54 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.16.7" - "@babel/plugin-transform-async-to-generator" "^7.16.8" + "@babel/plugin-transform-arrow-functions" "^7.17.12" + "@babel/plugin-transform-async-to-generator" "^7.17.12" "@babel/plugin-transform-block-scoped-functions" "^7.16.7" - "@babel/plugin-transform-block-scoping" "^7.16.7" - "@babel/plugin-transform-classes" "^7.16.7" - "@babel/plugin-transform-computed-properties" "^7.16.7" - "@babel/plugin-transform-destructuring" "^7.16.7" + "@babel/plugin-transform-block-scoping" "^7.17.12" + "@babel/plugin-transform-classes" "^7.17.12" + "@babel/plugin-transform-computed-properties" "^7.17.12" + "@babel/plugin-transform-destructuring" "^7.18.0" "@babel/plugin-transform-dotall-regex" "^7.16.7" - "@babel/plugin-transform-duplicate-keys" "^7.16.7" + "@babel/plugin-transform-duplicate-keys" "^7.17.12" "@babel/plugin-transform-exponentiation-operator" "^7.16.7" - "@babel/plugin-transform-for-of" "^7.16.7" + "@babel/plugin-transform-for-of" "^7.18.1" "@babel/plugin-transform-function-name" "^7.16.7" - "@babel/plugin-transform-literals" "^7.16.7" + "@babel/plugin-transform-literals" "^7.17.12" "@babel/plugin-transform-member-expression-literals" "^7.16.7" - "@babel/plugin-transform-modules-amd" "^7.16.7" - "@babel/plugin-transform-modules-commonjs" "^7.16.8" - "@babel/plugin-transform-modules-systemjs" "^7.16.7" - "@babel/plugin-transform-modules-umd" "^7.16.7" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.8" - "@babel/plugin-transform-new-target" "^7.16.7" + "@babel/plugin-transform-modules-amd" "^7.18.0" + "@babel/plugin-transform-modules-commonjs" "^7.18.2" + "@babel/plugin-transform-modules-systemjs" "^7.18.0" + "@babel/plugin-transform-modules-umd" "^7.18.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.17.12" + "@babel/plugin-transform-new-target" "^7.17.12" "@babel/plugin-transform-object-super" "^7.16.7" - "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.17.12" "@babel/plugin-transform-property-literals" "^7.16.7" - "@babel/plugin-transform-regenerator" "^7.16.7" - "@babel/plugin-transform-reserved-words" "^7.16.7" + "@babel/plugin-transform-regenerator" "^7.18.0" + "@babel/plugin-transform-reserved-words" "^7.17.12" "@babel/plugin-transform-shorthand-properties" "^7.16.7" - "@babel/plugin-transform-spread" "^7.16.7" + "@babel/plugin-transform-spread" "^7.17.12" "@babel/plugin-transform-sticky-regex" "^7.16.7" - "@babel/plugin-transform-template-literals" "^7.16.7" - "@babel/plugin-transform-typeof-symbol" "^7.16.7" + "@babel/plugin-transform-template-literals" "^7.18.2" + "@babel/plugin-transform-typeof-symbol" "^7.17.12" "@babel/plugin-transform-unicode-escapes" "^7.16.7" "@babel/plugin-transform-unicode-regex" "^7.16.7" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.16.8" + "@babel/types" "^7.18.2" babel-plugin-polyfill-corejs2 "^0.3.0" babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" - core-js-compat "^3.20.2" + core-js-compat "^3.22.1" semver "^6.3.0" "@babel/preset-flow@^7.12.1": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.16.7.tgz#7fd831323ab25eeba6e4b77a589f680e30581cbd" - integrity sha512-6ceP7IyZdUYQ3wUVqyRSQXztd1YmFHWI4Xv11MIqAlE4WqxBSd/FZ61V9k+TS5Gd4mkHOtQtPp9ymRpxH4y1Ug== + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.17.12.tgz#664a5df59190260939eee862800a255bef3bd66f" + integrity sha512-7QDz7k4uiaBdu7N89VKjUn807pJRXmdirQu0KyR9LXnQrr5Jt41eIMKTS7ljej+H29erwmMrwq9Io9mJHLI3Lw== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-flow-strip-types" "^7.16.7" + "@babel/plugin-transform-flow-strip-types" "^7.17.12" "@babel/preset-modules@^0.1.5": version "0.1.5" @@ -1196,26 +1183,26 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.12.13", "@babel/preset-react@^7.12.5", "@babel/preset-react@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.7.tgz#4c18150491edc69c183ff818f9f2aecbe5d93852" - integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== +"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.12.5", "@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.7": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.17.12.tgz#62adbd2d1870c0de3893095757ed5b00b492ab3d" + integrity sha512-h5U+rwreXtZaRBEQhW1hOJLMq8XNJBQ/9oymXiCXTuT/0uOwpbT0gUt+sXeOqoXBgNuUKI7TaObVwoEyWkpFgA== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-option" "^7.16.7" "@babel/plugin-transform-react-display-name" "^7.16.7" - "@babel/plugin-transform-react-jsx" "^7.16.7" + "@babel/plugin-transform-react-jsx" "^7.17.12" "@babel/plugin-transform-react-jsx-development" "^7.16.7" "@babel/plugin-transform-react-pure-annotations" "^7.16.7" -"@babel/preset-typescript@^7.12.16", "@babel/preset-typescript@^7.12.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" - integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== +"@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.7": + version "7.17.12" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz#40269e0a0084d56fc5731b6c40febe1c9a4a3e8c" + integrity sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-plugin-utils" "^7.17.12" "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-typescript" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.17.12" "@babel/register@^7.12.1": version "7.17.7" @@ -1228,15 +1215,23 @@ pirates "^4.0.5" source-map-support "^0.5.16" -"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.12.13": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.9.tgz#3d02d0161f0fbf3ada8e88159375af97690f4055" - integrity sha512-WxYHHUWF2uZ7Hp1K+D1xQgbgkGUfA+5UPOegEXGt2Y5SMog/rYCVaifLZDbw8UkNXozEqqrZTy6bglL7xTaCOw== +"@babel/runtime-corejs2@^7.17.8": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.18.3.tgz#415671a614d82e9791df7acf67b4853b96346643" + integrity sha512-r9L5eZChdP1DHSL+YwGEd7Rcs8Yk2KDOwSJ4cdB8ZAMolqiW7Qa9I1ir/05lte/i6N979PKYr5288OqoyZkabA== + dependencies: + core-js "^2.6.12" + regenerator-runtime "^0.13.4" + +"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.17.9": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.18.3.tgz#52f0241a31e0ec61a6187530af6227c2846bd60c" + integrity sha512-l4ddFwrc9rnR+EJsHsh+TJ4A35YqQz/UqcjtlX2ov53hlJYG5CxtQmNZxyajwDVmCxwy++rtvGU5HazCK4W41Q== dependencies: core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@7.1.2", "@babel/runtime@7.16.3", "@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@7.13.10", "@babel/runtime@7.16.3", "@babel/runtime@7.17.9", "@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.17.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.16.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== @@ -1252,26 +1247,26 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.6", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.13", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9", "@babel/traverse@^7.4.3": - version "7.17.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" - integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.6", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.10", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.4.3": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8" + integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA== dependencies: "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.9" - "@babel/helper-environment-visitor" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-environment-visitor" "^7.18.2" "@babel/helper-function-name" "^7.17.9" "@babel/helper-hoist-variables" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.9" - "@babel/types" "^7.17.0" + "@babel/parser" "^7.18.0" + "@babel/types" "^7.18.2" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.11", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" - integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== +"@babel/types@^7.0.0", "@babel/types@^7.12.11", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.17.12", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== dependencies: "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" @@ -1299,6 +1294,11 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@cornerstonejs/calculate-suv@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@cornerstonejs/calculate-suv/-/calculate-suv-1.0.2.tgz#c97ddba5ce3c0ee85798bb40df025b7d07075aea" + integrity sha512-xkd+NN+pbpz6bKT/lCRu/SRQd4R3GIDPx+QHMhngm9TN2JCRPjV7jigUC8csJb1mqhFbtW1kmBBoQE2CRX5IhA== + "@cornerstonejs/codec-charls@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-charls/-/codec-charls-0.1.1.tgz#e55d4aa908732d0cc902888b7f3856c5a996df7f" @@ -1314,7 +1314,48 @@ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-0.1.1.tgz#5bd1c52a33a425299299e970312731fa0cc2711b" integrity sha512-HOMMOLV6xy8O/agNGGvrl0a8DwShpBvWxAzEzv2pqq12d3r5z/3MyIgNA3Oj/8bIBVvvVXxh9RX7rMDRHJdowg== -"@csstools/postcss-color-function@^1.0.3": +"@cornerstonejs/core@^0.13.10": + version "0.13.10" + resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-0.13.10.tgz#d2aa3a9ba4951b6b6937a310548a5fe517a29d49" + integrity sha512-y5nCz4nvgg86Er3NovKK/1DikK1F+zye2gsYnMFE3YJVYcCeCcgFj/rdQxj2IYZc0E/gpyjyaKDMh0zAmuoPoQ== + dependencies: + detect-gpu "^4.0.7" + lodash.clonedeep "4.5.0" + +"@cornerstonejs/core@^0.13.11": + version "0.13.11" + resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-0.13.11.tgz#94a17a769e4fe7b5edcc69754bbf73135393931f" + integrity sha512-jNZmqFAVc9bpaor36pXNma06DqP+2NOj2EiZfO3fngR+t7vPOOhNz9axy8rFSXGIKZEOxGKLnnW8cL56lhSJ8A== + dependencies: + detect-gpu "^4.0.7" + lodash.clonedeep "4.5.0" + +"@cornerstonejs/streaming-image-volume-loader@^0.4.10": + version "0.4.10" + resolved "https://registry.yarnpkg.com/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.4.10.tgz#7f9ac1a0575c13af059f6cb18a578f1ee89d3cde" + integrity sha512-mcu/GaFDSz3xHWm5kfeLB8rr4UssJhkXgjH9bnRkJ0/qq+t64rRegZJafQz5XpAyoVOXyW7mb3oJuOh3J0HTZQ== + dependencies: + "@cornerstonejs/core" "^0.13.10" + cornerstone-wado-image-loader "^4.1.5" + +"@cornerstonejs/tools@^0.20.15": + version "0.20.15" + resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-0.20.15.tgz#493bd2d136e061b88cc2c66b63ef02cba791e55b" + integrity sha512-+NaNluG4xKIOXP4VOpU3LeibFncRoiWmQkAUKq0OuPpU+W0HR5krThlZ0OM6RQduOHChRMXOfvcnAtEu2AbzIQ== + dependencies: + "@cornerstonejs/core" "^0.13.11" + lodash.clonedeep "4.5.0" + lodash.get "^4.4.2" + +"@csstools/postcss-cascade-layers@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.0.2.tgz#7c48b5f773c4cdcdc6b57d6099fbdc2332e12219" + integrity sha512-n5fSd3N/RTLjwC6TLnHjlVEt5tRg6S6Pu+LpRgXayX0QVJHvlMzE3+R12cd2F0we8WB4OE8o5r5iWgmBPpqUyQ== + dependencies: + "@csstools/selector-specificity" "^1.0.0" + postcss-selector-parser "^6.0.10" + +"@csstools/postcss-color-function@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.0.tgz#229966327747f58fbe586de35daa139db3ce1e5d" integrity sha512-5D5ND/mZWcQoSfYnSPsXtuiFxhzmhxt6pcjrFLJyldj+p0ZN2vvRpYNX+lahFTtMhAYOa2WmkdGINr0yP0CvGA== @@ -1329,10 +1370,10 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-hwb-function@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.0.tgz#d6785c1c5ba8152d1d392c66f3a6a446c6034f6d" - integrity sha512-VSTd7hGjmde4rTj1rR30sokY3ONJph1reCBTUXqeW1fKwETPy1x4t/XIeaaqbMbC5Xg4SM/lyXZ2S8NELT2TaA== +"@csstools/postcss-hwb-function@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.1.tgz#5224db711ed09a965f85c80c18144ac1c2702fce" + integrity sha512-AMZwWyHbbNLBsDADWmoXT9A5yl5dsGEBeJSJRUJt8Y9n8Ziu7Wstt4MC8jtPW7xjcLecyfJwtnUTNSmOzcnWeg== dependencies: postcss-value-parser "^4.2.0" @@ -1344,11 +1385,12 @@ "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" -"@csstools/postcss-is-pseudo-class@^2.0.1": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.2.tgz#a834ca11a43d6ed9bc9e3ff53c80d490a4b1aaad" - integrity sha512-L9h1yxXMj7KpgNzlMrw3isvHJYkikZgZE4ASwssTnGEH8tm50L6QsM9QQT5wR4/eO5mU0rN5axH7UzNxEYg5CA== +"@csstools/postcss-is-pseudo-class@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.4.tgz#6e8b49b96a7d3346d5316bd773dcff9c983b4183" + integrity sha512-T2Tmr5RIxkCEXxHwMVyValqwv3h5FTJPpmU8Mq/HDV+TY6C9srVaNMiMG/sp0QaxUnVQQrnXsuLU+1g2zrLDcQ== dependencies: + "@csstools/selector-specificity" "^1.0.0" postcss-selector-parser "^6.0.10" "@csstools/postcss-normalize-display-values@^1.0.0": @@ -1358,7 +1400,7 @@ dependencies: postcss-value-parser "^4.2.0" -"@csstools/postcss-oklab-function@^1.0.2": +"@csstools/postcss-oklab-function@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.0.tgz#e9a269487a292e0930760948e923e1d46b638ee6" integrity sha512-e/Q5HopQzmnQgqimG9v3w2IG4VRABsBq3itOcn4bnm+j4enTgQZ0nWsaH/m9GV2otWGQ0nwccYL5vmLKyvP1ww== @@ -1373,17 +1415,31 @@ dependencies: postcss-value-parser "^4.2.0" -"@cypress/listr-verbose-renderer@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" - integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= +"@csstools/postcss-stepped-value-functions@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.0.tgz#f8ffc05e163ba7bcbefc5fdcaf264ce9fd408c16" + integrity sha512-q8c4bs1GumAiRenmFjASBcWSLKrbzHzWl6C2HcaAxAXIiL2rUlUWbqQZUjwVG5tied0rld19j/Mm90K3qI26vw== dependencies: - chalk "^1.1.3" - cli-cursor "^1.0.2" - date-fns "^1.27.2" - figures "^1.7.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-trigonometric-functions@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.1.tgz#e36e61f445614193dbf6d3a8408709b0cf184a6f" + integrity sha512-G78CY/+GePc6dDCTUbwI6TTFQ5fs3N9POHhI6v0QzteGpf6ylARiJUNz9HrRKi4eVYBNXjae1W2766iUEFxHlw== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-unset-value@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.1.tgz#2cc020785db5ec82cc9444afe4cdae2a65445f89" + integrity sha512-f1G1WGDXEU/RN1TWAxBPQgQudtLnLQPyiWdtypkPC+mVYNKFKH/HYXSxH4MVNqwF8M0eDsoiU7HumJHCg/L/jg== + +"@csstools/selector-specificity@1.0.0", "@csstools/selector-specificity@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-1.0.0.tgz#91c560df2ed8d9700e4c7ed4ac21a3a322c9d975" + integrity sha512-RkYG5KiGNX0fJ5YoI0f4Wfq2Yo74D25Hru4fxTOioYdQvHBxcrrtTTyT5Ozzh2ejcNrhFy7IEts2WyEY7yi5yw== -"@cypress/request@^2.88.5": +"@cypress/request@^2.88.10": version "2.88.10" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== @@ -1415,471 +1471,493 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - "@discoveryjs/json-ext@^0.5.0", "@discoveryjs/json-ext@^0.5.3": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@docsearch/css@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0.tgz#fe57b474802ffd706d3246eab25d52fac8aa3698" - integrity sha512-1kkV7tkAsiuEd0shunYRByKJe3xQDG2q7wYg24SOw1nV9/2lwEd4WrUYRJC/ukGTl2/kHeFxsaUvtiOy0y6fFA== +"@docsearch/css@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.1.0.tgz#6781cad43fc2e034d012ee44beddf8f93ba21f19" + integrity sha512-bh5IskwkkodbvC0FzSg1AxMykfDl95hebEKwxNoq4e5QaGzOXSBgW8+jnMFZ7JU4sTBiB04vZWoUSzNrPboLZA== -"@docsearch/react@^3.0.0-alpha.36": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0.tgz#d02ebdc67573412185a6a4df13bc254c7c0da491" - integrity sha512-yhMacqS6TVQYoBh/o603zszIb5Bl8MIXuOc6Vy617I74pirisDzzcNh0NEaYQt50fVVR3khUbeEhUEWEWipESg== +"@docsearch/react@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.1.0.tgz#da943a64c01ee82b04e53b691806469272f943f7" + integrity sha512-bjB6ExnZzf++5B7Tfoi6UXgNwoUnNOfZ1NyvnvPhWgCMy5V/biAtLL4o7owmZSYdAKeFSvZ5Lxm0is4su/dBWg== dependencies: - "@algolia/autocomplete-core" "1.5.2" - "@algolia/autocomplete-preset-algolia" "1.5.2" - "@docsearch/css" "3.0.0" + "@algolia/autocomplete-core" "1.6.3" + "@docsearch/css" "3.1.0" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.3.tgz#3af14897dcf5b73554314f6ed02c46cf0f463336" - integrity sha512-vzKmQsvOCte9odf0ZRU2h5UzdI1km5D0NU3Ee6xn06VydYZ169B1IF5KV1LWHSYklnsEmzizJ/jeopFCry0cGg== +"@docusaurus/core@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.20.tgz#cf4aeeccecacb547a6fb42340c83bed0cccb57c0" + integrity sha512-a3UgZ4lIcIOoZd4j9INqVkWSXEDxR7EicJXt8eq2whg4N5hKGqLHoDSnWfrVSPQn4NoG5T7jhPypphSoysImfQ== dependencies: - "@babel/core" "^7.12.16" - "@babel/generator" "^7.12.15" + "@babel/core" "^7.17.10" + "@babel/generator" "^7.17.10" "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.12.15" - "@babel/preset-env" "^7.12.16" - "@babel/preset-react" "^7.12.13" - "@babel/preset-typescript" "^7.12.16" - "@babel/runtime" "^7.12.5" - "@babel/runtime-corejs3" "^7.12.13" - "@babel/traverse" "^7.12.13" - "@docusaurus/cssnano-preset" "2.0.0-beta.3" - "@docusaurus/react-loadable" "5.5.0" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-common" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - "@slorber/static-site-generator-webpack-plugin" "^4.0.0" - "@svgr/webpack" "^5.5.0" - autoprefixer "^10.2.5" - babel-loader "^8.2.2" + "@babel/plugin-transform-runtime" "^7.17.10" + "@babel/preset-env" "^7.17.10" + "@babel/preset-react" "^7.16.7" + "@babel/preset-typescript" "^7.16.7" + "@babel/runtime" "^7.17.9" + "@babel/runtime-corejs3" "^7.17.9" + "@babel/traverse" "^7.17.10" + "@docusaurus/cssnano-preset" "2.0.0-beta.20" + "@docusaurus/logger" "2.0.0-beta.20" + "@docusaurus/mdx-loader" "2.0.0-beta.20" + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-common" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + "@slorber/static-site-generator-webpack-plugin" "^4.0.4" + "@svgr/webpack" "^6.2.1" + autoprefixer "^10.4.5" + babel-loader "^8.2.5" babel-plugin-dynamic-import-node "2.3.0" - boxen "^5.0.1" - chalk "^4.1.1" - chokidar "^3.5.1" - clean-css "^5.1.2" + boxen "^6.2.1" + chokidar "^3.5.3" + clean-css "^5.3.0" + cli-table3 "^0.6.2" + combine-promises "^1.1.0" commander "^5.1.0" - copy-webpack-plugin "^9.0.0" - core-js "^3.9.1" - css-loader "^5.1.1" - css-minimizer-webpack-plugin "^3.0.1" - cssnano "^5.0.4" + copy-webpack-plugin "^10.2.4" + core-js "^3.22.3" + css-loader "^6.7.1" + css-minimizer-webpack-plugin "^3.4.1" + cssnano "^5.1.7" del "^6.0.0" detect-port "^1.3.0" escape-html "^1.0.3" - eta "^1.12.1" - express "^4.17.1" + eta "^1.12.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - github-slugger "^1.3.0" - globby "^11.0.2" - html-minifier-terser "^5.1.1" - html-tags "^3.1.0" - html-webpack-plugin "^5.3.2" + fs-extra "^10.1.0" + html-minifier-terser "^6.1.0" + html-tags "^3.2.0" + html-webpack-plugin "^5.5.0" import-fresh "^3.3.0" - is-root "^2.1.0" leven "^3.1.0" - lodash "^4.17.20" - mini-css-extract-plugin "^1.6.0" - module-alias "^2.2.2" - nprogress "^0.2.0" - postcss "^8.2.15" - postcss-loader "^5.3.0" - prompts "^2.4.1" - react-dev-utils "^11.0.1" - react-error-overlay "^6.0.9" - react-helmet "^6.1.0" - react-loadable "^5.5.0" + lodash "^4.17.21" + mini-css-extract-plugin "^2.6.0" + postcss "^8.4.13" + postcss-loader "^6.2.1" + prompts "^2.4.2" + react-dev-utils "^12.0.1" + react-helmet-async "^1.3.0" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" react-loadable-ssr-addon-v5-slorber "^1.0.1" react-router "^5.2.0" react-router-config "^5.1.1" react-router-dom "^5.2.0" - resolve-pathname "^3.0.0" - rtl-detect "^1.0.3" - semver "^7.3.4" + remark-admonitions "^1.2.1" + rtl-detect "^1.0.4" + semver "^7.3.7" serve-handler "^6.1.3" - shelljs "^0.8.4" - std-env "^2.2.1" - strip-ansi "^6.0.0" - terser-webpack-plugin "^5.1.3" - tslib "^2.2.0" + shelljs "^0.8.5" + terser-webpack-plugin "^5.3.1" + tslib "^2.4.0" update-notifier "^5.1.0" url-loader "^4.1.1" - wait-on "^5.3.0" - webpack "^5.40.0" - webpack-bundle-analyzer "^4.4.2" - webpack-dev-server "^3.11.2" + wait-on "^6.0.1" + webpack "^5.72.0" + webpack-bundle-analyzer "^4.5.0" + webpack-dev-server "^4.8.1" webpack-merge "^5.8.0" - webpackbar "^5.0.0-3" + webpackbar "^5.0.2" -"@docusaurus/cssnano-preset@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.3.tgz#b82d58df4e95b04cd57a3e9922d39056207d2c73" - integrity sha512-k7EkNPluB+TV++oZB8Je4EQ6Xs6cR0SvgIU9vdXm00qyPCu38MMfRwSY4HnsVUV797T/fQUD91zkuwhyXCUGLA== +"@docusaurus/cssnano-preset@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.20.tgz#c47722e347fd044f2372e924199eabf61733232f" + integrity sha512-7pfrYuahHl3YYS+gYhbb1YHsq5s5+hk+1KIU7QqNNn4YjrIqAHlOznCQ9XfQfspe9boZmaNFGMZQ1tawNOVLqQ== dependencies: - cssnano-preset-advanced "^5.1.1" - postcss "^8.2.15" - postcss-sort-media-queries "^3.10.11" + cssnano-preset-advanced "^5.3.3" + postcss "^8.4.13" + postcss-sort-media-queries "^4.2.1" + +"@docusaurus/logger@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.20.tgz#bb49e8516e48082ab96bca11569a18541476d5a6" + integrity sha512-7Rt7c8m3ZM81o5jsm6ENgdbjq/hUICv8Om2i7grynI4GT2aQyFoHcusaNbRji4FZt0DaKT2CQxiAWP8BbD4xzQ== + dependencies: + chalk "^4.1.2" + tslib "^2.4.0" -"@docusaurus/lqip-loader@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/lqip-loader/-/lqip-loader-2.0.0-beta.3.tgz#1312e55f163c99fbc440832578dfea21f0256e5b" - integrity sha512-YrSP9jG+3WUyt6ln9DVLSnl0Fq/kID6juYbUdsq6H/QlyF/Tuu3acBpaYYQHWCAUZlqbnZ9uerKblY4+PN1SYA== +"@docusaurus/lqip-loader@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/lqip-loader/-/lqip-loader-2.0.0-beta.20.tgz#9552d3276dbc4e7840586db0bc6c6e4428cde375" + integrity sha512-ZIrSZmObxpiV1HhoEe1o+0GJzjC/4hP8lDfja2aaMI8jGG5PZViTrUe4KNZpFPpD7OT2Gv7/pbtacjfAQt7hFg== dependencies: + "@docusaurus/logger" "2.0.0-beta.20" file-loader "^6.2.0" - lodash "^4.17.20" - node-vibrant "^3.1.5" - sharp "^0.28.2" - -"@docusaurus/mdx-loader@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.3.tgz#e1d9f3265889e4728412dab9f45d940bd87639fd" - integrity sha512-xH6zjNokZD2D7Y+Af3gMO692lwfw5N3NzxuLqMF3D0HPHOLrokDeIeVPeY/EBJBxZiXgqWGZ/ESewNDU1ZUfRQ== - dependencies: - "@babel/parser" "^7.12.16" - "@babel/traverse" "^7.12.13" - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" + lodash "^4.17.21" + sharp "^0.30.4" + tslib "^2.4.0" + +"@docusaurus/mdx-loader@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.20.tgz#7016e8ce7e4a11c9ea458e2236d3c93af71f393f" + integrity sha512-BBuf77sji3JxbCEW7Qsv3CXlgpm+iSLTQn6JUK7x8vJ1JYZ3KJbNgpo9TmxIIltpcvNQ/QOy6dvqrpSStaWmKQ== + dependencies: + "@babel/parser" "^7.17.10" + "@babel/traverse" "^7.17.10" + "@docusaurus/logger" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@mdx-js/mdx" "^1.6.22" escape-html "^1.0.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - github-slugger "^1.3.0" - gray-matter "^4.0.3" + fs-extra "^10.1.0" + image-size "^1.0.1" mdast-util-to-string "^2.0.0" - remark-emoji "^2.1.0" + remark-emoji "^2.2.0" stringify-object "^3.3.0" - unist-util-visit "^2.0.2" + tslib "^2.4.0" + unist-util-visit "^2.0.3" url-loader "^4.1.1" - webpack "^5.40.0" - -"@docusaurus/plugin-client-redirects@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.0.0-beta.3.tgz#c5e70286cd60557fce3b60c48b2c0a976e71e5fc" - integrity sha512-15lU21XYFC3afH2OFCa7Qul6yOdDPDiYJTJg3mOE3h0mzXlbrHeOkkbMJTwbSLmktwyiCxORFJX0zRk1/zXsow== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-common" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - chalk "^4.1.1" - eta "^1.11.0" - fs-extra "^10.0.0" - globby "^11.0.2" - lodash "^4.17.20" - tslib "^2.2.0" - -"@docusaurus/plugin-content-blog@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.3.tgz#cb7ea4c61ee8717a76595b339932e7ee9fe9e091" - integrity sha512-QynxHVzS3jItnDbmu9wkASyMxrduauqONVqYHrL4x2pC4kzSTIrcDnOK1JXUJAuDg9XY66ISWQ8dN7YZOpU+4Q== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/mdx-loader" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - chalk "^4.1.1" - escape-string-regexp "^4.0.0" + webpack "^5.72.0" + +"@docusaurus/module-type-aliases@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.0-beta.20.tgz#669605a64b04226c391e0284c44743e137eb2595" + integrity sha512-lUIXLwQEOyYwcb3iCNibPUL6O9ijvYF5xQwehGeVraTEBts/Ch8ZwELFk+XbaGHKh52PiVxuWL2CP4Gdjy5QKw== + dependencies: + "@docusaurus/types" "2.0.0-beta.20" + "@types/react" "*" + "@types/react-router-config" "*" + "@types/react-router-dom" "*" + react-helmet-async "*" + +"@docusaurus/plugin-client-redirects@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.0.0-beta.20.tgz#f4bbe5958bbcc7915968c23d99dcfcbfe82df8d2" + integrity sha512-YgIDFpr2EDpLwYUtq1t6VD7EOsD3YqA6biXkQmgts1gjFZ1pqVte6Q+tXVzwvL/xR7NdnekevygmS98/IPNePQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/logger" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-common" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + eta "^1.12.3" + fs-extra "^10.1.0" + lodash "^4.17.21" + tslib "^2.4.0" + +"@docusaurus/plugin-content-blog@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.20.tgz#7c0d413ac8df9a422a0b3ddd90b77ec491bbda15" + integrity sha512-6aby36Gmny5h2oo/eEZ2iwVsIlBWbRnNNeqT0BYnJO5aj53iCU/ctFPpJVYcw0l2l8+8ITS70FyePIWEsaZ0jA== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/logger" "2.0.0-beta.20" + "@docusaurus/mdx-loader" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-common" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + cheerio "^1.0.0-rc.10" feed "^4.2.2" - fs-extra "^10.0.0" - globby "^11.0.2" - loader-utils "^2.0.0" - lodash "^4.17.20" - reading-time "^1.3.0" + fs-extra "^10.1.0" + lodash "^4.17.21" + reading-time "^1.5.0" remark-admonitions "^1.2.1" - tslib "^2.2.0" - webpack "^5.40.0" - -"@docusaurus/plugin-content-docs@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.3.tgz#8c4f1780c1264fcb5937183ab8ce9792f88f253a" - integrity sha512-lB9UjDyFtq89tpyif+JDIJ/gtwtSTEwOBNTLAzOsg4ZIfNLfyifrWN4ci0TkZV0xShWUHqGp36/5XTpHRn1jJQ== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/mdx-loader" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - chalk "^4.1.1" + tslib "^2.4.0" + unist-util-visit "^2.0.3" + utility-types "^3.10.0" + webpack "^5.72.0" + +"@docusaurus/plugin-content-docs@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.20.tgz#2a53b9fc355f45bf7c6917f19be7bfa0698b8b9e" + integrity sha512-XOgwUqXtr/DStpB3azdN6wgkKtQkOXOx1XetORzhHnjihrSMn6daxg+spmcJh1ki/mpT3n7yBbKJxVNo+VB38Q== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/logger" "2.0.0-beta.20" + "@docusaurus/mdx-loader" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" combine-promises "^1.1.0" - escape-string-regexp "^4.0.0" - execa "^5.0.0" - fs-extra "^10.0.0" - globby "^11.0.2" - import-fresh "^3.2.2" - js-yaml "^4.0.0" - loader-utils "^1.2.3" - lodash "^4.17.20" + fs-extra "^10.1.0" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + lodash "^4.17.21" remark-admonitions "^1.2.1" - shelljs "^0.8.4" - tslib "^2.2.0" + tslib "^2.4.0" utility-types "^3.10.0" - webpack "^5.40.0" - -"@docusaurus/plugin-content-pages@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.3.tgz#f90d565563551556354b8dbf48ab5765c3b36fa2" - integrity sha512-lV6ZoSkkVwN10kQLE4sEAubaEnzXjKBQBhMCx49wkrvRwKzjBoRnfWV8qAswN1KU2YZZL1ixFpcr8+oXvhxkuA== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/mdx-loader" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - globby "^11.0.2" - lodash "^4.17.20" - minimatch "^3.0.4" + webpack "^5.72.0" + +"@docusaurus/plugin-content-pages@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.20.tgz#6a76c7fa049983d2d94d8c4d738802acf01611fe" + integrity sha512-ubY6DG4F0skFKjfNGCbfO34Qf+MZy6C05OtpIYsoA2YU8ADx0nRH7qPgdEkwR3ma860DbY612rleRT13ogSlhg== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/mdx-loader" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + fs-extra "^10.1.0" remark-admonitions "^1.2.1" - slash "^3.0.0" - tslib "^2.1.0" - webpack "^5.40.0" + tslib "^2.4.0" + webpack "^5.72.0" -"@docusaurus/plugin-debug@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.3.tgz#2278cb537cb6acf153977733c19c704a32e15b28" - integrity sha512-EeHUcCPsr9S1tsyRo42SnhrCCOlcvkcA8CR4pOofiJkG1gJ8IwhY9fNOLJM7dYs0bMtViiqXy5fD2jUib4G1jw== +"@docusaurus/plugin-debug@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.20.tgz#7c026e81c45fd4f00801a785c928b9e8727a99de" + integrity sha512-acGZmpncPA1XDczpV1ji1ajBCRBY/H2lXN8alSjOB1vh0c/2Qz+KKD05p17lsUbhIyvsnZBa/BaOwtek91Lu7Q== dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + fs-extra "^10.1.0" react-json-view "^1.21.3" - tslib "^2.1.0" - -"@docusaurus/plugin-google-analytics@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.3.tgz#aff4a625b183c2c5bcb63ce61542592e98075f55" - integrity sha512-e6tO1FCIdAqIjcLAUaHugz/dErAP/wx67WyN6bWSdAMJRobmav+TFesE2iVzzIMxuRB3pY0Y7TtLL5dF5xpIsg== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - -"@docusaurus/plugin-google-gtag@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.3.tgz#e52105a749e33e6ea44e7103f42df0fcac0268ac" - integrity sha512-p48CK7ZwThs9wc/UEv+zG3lZ/Eh4Rwg2c0MBBLYATGE+Wwh6HIyilhjQAj4dC6wf9iYvCZFXX2pNOr+cKKafIA== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - -"@docusaurus/plugin-ideal-image@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-ideal-image/-/plugin-ideal-image-2.0.0-beta.3.tgz#b22f68e060bbf82a6863a3911f1f420b3cdf7ca8" - integrity sha512-OwyufquVmZSWOrZrYdIg7ArW2JTT4w3IOUlddzEMeG9gIGvI5/50sT+OaPKhZQ73rBiJjuRpF1CcBQa0RNxDOQ== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/lqip-loader" "2.0.0-beta.3" - "@docusaurus/responsive-loader" "1.4.0" - "@docusaurus/types" "2.0.0-beta.3" + tslib "^2.4.0" + +"@docusaurus/plugin-google-analytics@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.20.tgz#c1bdbc1239f987f9716fa30c2fd86962c190a765" + integrity sha512-4C5nY25j0R1lntFmpSEalhL7jYA7tWvk0VZObiIxGilLagT/f9gWPQtIjNBe4yzdQvkhiaXpa8xcMcJUAKRJyw== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + tslib "^2.4.0" + +"@docusaurus/plugin-google-gtag@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.20.tgz#7284bcfad9cb4e5d63605e95f6da73ca8ac8f053" + integrity sha512-EMZdiMTNg4NwE60xwjbetcqMDqAOazMTwQAQ4OuNAclv7oh8+VPCvqRF8s8AxCoI2Uqc7vh8yzNUuM307Ne9JA== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + tslib "^2.4.0" + +"@docusaurus/plugin-ideal-image@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-ideal-image/-/plugin-ideal-image-2.0.0-beta.20.tgz#f6a562661f41e2f396e854e3aaa830b67f6dbc0d" + integrity sha512-IbBDVGqmdSeY+HdeJ+s9E75ceZbM7RHzyBkbNTmCDeRdnlbol4IeOPp640LS4+RNQV1i3802je0m6n4Sf/ZPcA== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/lqip-loader" "2.0.0-beta.20" + "@docusaurus/responsive-loader" "^1.7.0" + "@docusaurus/theme-translations" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" "@endiliey/react-ideal-image" "^0.0.11" react-waypoint "^10.1.0" - sharp "^0.28.2" - tslib "^2.1.0" - webpack "^5.40.0" - -"@docusaurus/plugin-pwa@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-pwa/-/plugin-pwa-2.0.0-beta.3.tgz#9f7a2bfdb63e7dfe0f787a354df218ac04a97919" - integrity sha512-a6x5Le2BxH/Gs7VvjfjAsbUu1ol3qCpB3D47LtNzYpzXMr+oCE+dHOKJLm2NQ2zNEcQQ+3so4I+sfcjVLd+/AQ== - dependencies: - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.13" - "@babel/plugin-proposal-optional-chaining" "^7.12.16" - "@babel/preset-env" "^7.12.16" - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/theme-common" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - babel-loader "^8.2.2" + sharp "^0.30.4" + tslib "^2.4.0" + webpack "^5.72.0" + +"@docusaurus/plugin-pwa@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-pwa/-/plugin-pwa-2.0.0-beta.20.tgz#58f69a653f7a3a388cc76f9422e5e0399bef1710" + integrity sha512-14QsdnjwZOnlNLyeSkqQ+3M624IXNajyKKAloLwZMjH9QUvBgBpd3ix1N8BXVdVvLFFrzW9U2FttWcdxpmCU+g== + dependencies: + "@babel/core" "^7.17.10" + "@babel/preset-env" "^7.17.10" + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/theme-common" "2.0.0-beta.20" + "@docusaurus/theme-translations" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + babel-loader "^8.2.5" clsx "^1.1.1" - core-js "^2.6.5" - terser-webpack-plugin "^5.1.3" - webpack "^5.40.0" - webpack-merge "^5.7.3" - workbox-build "^6.1.1" - workbox-precaching "^6.1.1" - workbox-window "^6.1.1" - -"@docusaurus/plugin-sitemap@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.3.tgz#61e0b52db51cc11dd59662041ffd32ef3f4ec5f1" - integrity sha512-ilEJ3Xb8zbShjGhdRHGAm4OZ0bUwFxtMtcTyqLlGmk9r0U2h0CWcaS+geJfLwgUJkwgKZfGdDrmTpmf8oeGQvw== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-common" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - fs-extra "^10.0.0" - sitemap "^7.0.0" - tslib "^2.2.0" - -"@docusaurus/preset-classic@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.3.tgz#44fe60bb73e671cce56a028c8731d97631fe57b2" - integrity sha512-32B/7X3H8XX5jBqg23veEqNJ0JtKCG0Va+7wTX9+B36tMyPnsq3H3m0m5XICfX/NGfPICfjw/oCN2CEAYFd47Q== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/plugin-content-blog" "2.0.0-beta.3" - "@docusaurus/plugin-content-docs" "2.0.0-beta.3" - "@docusaurus/plugin-content-pages" "2.0.0-beta.3" - "@docusaurus/plugin-debug" "2.0.0-beta.3" - "@docusaurus/plugin-google-analytics" "2.0.0-beta.3" - "@docusaurus/plugin-google-gtag" "2.0.0-beta.3" - "@docusaurus/plugin-sitemap" "2.0.0-beta.3" - "@docusaurus/theme-classic" "2.0.0-beta.3" - "@docusaurus/theme-search-algolia" "2.0.0-beta.3" - -"@docusaurus/react-loadable@5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.0.tgz#6d6f0c8fd9a434b62a1ab1f8645ee7bde5a9ec21" - integrity sha512-Ld/kwUE6yATIOTLq3JCsWiTa/drisajwKqBQ2Rw6IcT+sFsKfYek8F2jSH8f68AT73xX97UehduZeCSlnuCBIg== + core-js "^3.22.3" + terser-webpack-plugin "^5.3.1" + tslib "^2.4.0" + webpack "^5.72.0" + webpack-merge "^5.8.0" + workbox-build "^6.5.3" + workbox-precaching "^6.5.3" + workbox-window "^6.5.3" + +"@docusaurus/plugin-sitemap@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.20.tgz#08eada1260cafb273abea33c9c890ab667c7cbd3" + integrity sha512-Rf5a2vOBWjbe7PJJEBDeLZzDA7lsDi+16bqzKN8OKSXlcZLhxjmIpL5NrjANNbpGpL5vbl9z+iqvjbQmZ3QSmA== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-common" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + fs-extra "^10.1.0" + sitemap "^7.1.1" + tslib "^2.4.0" + +"@docusaurus/preset-classic@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.20.tgz#19566be713ce0297834cd999392ea3605bc6f574" + integrity sha512-artUDjiYFIlGd2fxk0iqqcJ5xSCrgormOAoind1c0pn8TRXY1WSCQWYI6p4X24jjhSCzLv0s6Z9PMDyxZdivhg== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/plugin-content-blog" "2.0.0-beta.20" + "@docusaurus/plugin-content-docs" "2.0.0-beta.20" + "@docusaurus/plugin-content-pages" "2.0.0-beta.20" + "@docusaurus/plugin-debug" "2.0.0-beta.20" + "@docusaurus/plugin-google-analytics" "2.0.0-beta.20" + "@docusaurus/plugin-google-gtag" "2.0.0-beta.20" + "@docusaurus/plugin-sitemap" "2.0.0-beta.20" + "@docusaurus/theme-classic" "2.0.0-beta.20" + "@docusaurus/theme-common" "2.0.0-beta.20" + "@docusaurus/theme-search-algolia" "2.0.0-beta.20" + +"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== dependencies: + "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/remark-plugin-npm2yarn@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/remark-plugin-npm2yarn/-/remark-plugin-npm2yarn-2.0.0-beta.3.tgz#572f63704b3bd7e3ed928ffec62f133d84aafe92" - integrity sha512-LfQLILNCsqorw1qQG+j+wBMBF7eKDFN9pYG1ei0UWB9pmHM3DcEUAFi/WVkVR9zQAXYJUne5Ww4Ny5Hpfmcb4g== +"@docusaurus/remark-plugin-npm2yarn@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/remark-plugin-npm2yarn/-/remark-plugin-npm2yarn-2.0.0-beta.20.tgz#4c0d6721b53efbcaf772c69f117115becc1122c3" + integrity sha512-kMys0puRJ6iNx5XFZyll6BnbglfmJVqWHgwQLR7OGBw190dYNi1xH85Mi7dOjogrZO5k/gYk6MOo2xvivUd4rQ== dependencies: npm-to-yarn "^1.0.1" + tslib "^2.4.0" + unist-util-visit "^2.0.3" -"@docusaurus/responsive-loader@1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/responsive-loader/-/responsive-loader-1.4.0.tgz#dea284e15133570f5ef09be414881c65c42e43d9" - integrity sha512-qkVjSEUM4dVNmgOWkjbuRetZegOiCnzGPOzS1FPmxMrc9jCI70Rusx67EYdYPyAu47F2cvUf8PwX0o6AUzfNTQ== +"@docusaurus/responsive-loader@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@docusaurus/responsive-loader/-/responsive-loader-1.7.0.tgz#508df2779e04311aa2a38efb67cf743109afd681" + integrity sha512-N0cWuVqTRXRvkBxeMQcy/OF2l7GN8rmni5EzR3HpwR+iU2ckYPnziceojcxvvxQ5NqZg1QfEW0tycQgHp+e+Nw== dependencies: loader-utils "^2.0.0" -"@docusaurus/theme-classic@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.3.tgz#a84241ad6dc22aec9a33136e9d592034aea4d865" - integrity sha512-d2I4r9FQ67hCTGq+fkz0tDNvpCLxm/HAtjuu+XsZkX6Snh50XpWYfwOD4w8oFbbup5Imli2q7Z8Q2+9izphizw== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/plugin-content-blog" "2.0.0-beta.3" - "@docusaurus/plugin-content-docs" "2.0.0-beta.3" - "@docusaurus/plugin-content-pages" "2.0.0-beta.3" - "@docusaurus/theme-common" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-common" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" - chalk "^4.1.1" +"@docusaurus/theme-classic@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.20.tgz#c4c7712c2b35c5654433228729f92ec73e6c598e" + integrity sha512-rs4U68x8Xk6rPsZC/7eaPxCKqzXX1S45FICKmq/IZuaDaQyQIijCvv2ssxYnUyVZUNayZfJK7ZtNu+A0kzYgSQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/plugin-content-blog" "2.0.0-beta.20" + "@docusaurus/plugin-content-docs" "2.0.0-beta.20" + "@docusaurus/plugin-content-pages" "2.0.0-beta.20" + "@docusaurus/theme-common" "2.0.0-beta.20" + "@docusaurus/theme-translations" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-common" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + "@mdx-js/react" "^1.6.22" clsx "^1.1.1" copy-text-to-clipboard "^3.0.1" - fs-extra "^10.0.0" - globby "^11.0.2" - infima "0.2.0-alpha.26" - lodash "^4.17.20" - parse-numeric-range "^1.2.0" - postcss "^8.2.15" - prism-react-renderer "^1.2.1" - prismjs "^1.23.0" - prop-types "^15.7.2" + infima "0.2.0-alpha.39" + lodash "^4.17.21" + nprogress "^0.2.0" + postcss "^8.4.13" + prism-react-renderer "^1.3.1" + prismjs "^1.28.0" react-router-dom "^5.2.0" - rtlcss "^3.1.2" - -"@docusaurus/theme-common@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.3.tgz#8dbe05f3436637b099aeada5c75a6f7c9bc163a5" - integrity sha512-XuiqpfQyOWGniN7d8uMfUQ3OmCc70u+O0ObPUONj7gFglCzwu33Izx05gNrV9ekhnpQ1pkPcvGU7Soe9Hc5i6g== - dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/plugin-content-blog" "2.0.0-beta.3" - "@docusaurus/plugin-content-docs" "2.0.0-beta.3" - "@docusaurus/plugin-content-pages" "2.0.0-beta.3" - "@docusaurus/types" "2.0.0-beta.3" - tslib "^2.1.0" + rtlcss "^3.5.0" + +"@docusaurus/theme-common@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.20.tgz#4ec7d77ecd2ade9dad33b8689e3cd51b07e594b0" + integrity sha512-lmdGB3/GQM5z0GH0iHGRXUco4Wfqc6sR5eRKuW4j0sx3+UFVvtbVTTIGt0Cie4Dh6omnFxjPbNDlPDgWr/agVQ== + dependencies: + "@docusaurus/module-type-aliases" "2.0.0-beta.20" + "@docusaurus/plugin-content-blog" "2.0.0-beta.20" + "@docusaurus/plugin-content-docs" "2.0.0-beta.20" + "@docusaurus/plugin-content-pages" "2.0.0-beta.20" + clsx "^1.1.1" + parse-numeric-range "^1.3.0" + prism-react-renderer "^1.3.1" + tslib "^2.4.0" + utility-types "^3.10.0" -"@docusaurus/theme-live-codeblock@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-2.0.0-beta.3.tgz#c7fc9241356b8a6aef17843c16a2a800c9d68e20" - integrity sha512-L2rFxjHSNIs1tu6EyxS6EVp2/+7FjTv6r8MABikToR2mW71NZDSsGRgiWu7WWE2YO79JuvjwcALdxz+GOiJs/Q== +"@docusaurus/theme-live-codeblock@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-live-codeblock/-/theme-live-codeblock-2.0.0-beta.20.tgz#e414a55d0c95045658aac84965510953969791eb" + integrity sha512-dAo7HVveO1GTHjvmz0wL+ZmHOU4sXqzYEqfTN4wZElaDALrv7zBSsHljXxCM29gBm4WsX44hOPgPCRUa6+HVAw== dependencies: - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/theme-common" "2.0.0-beta.20" + "@docusaurus/theme-translations" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" "@philpl/buble" "^0.19.7" clsx "^1.1.1" - parse-numeric-range "^1.2.0" - prism-react-renderer "^1.2.1" - react-live "^2.2.3" - -"@docusaurus/theme-search-algolia@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.3.tgz#9ca8142fe4686d262d546c9ba309d02d9d4d59a9" - integrity sha512-fxWxcXGmqjwuA7zYRAbwqSANx3PVVjYUehV9SI28u5qq8U2tSYflhd1nGogM6guiV+Er6u8gwO91PL6wg3/vBA== - dependencies: - "@docsearch/react" "^3.0.0-alpha.36" - "@docusaurus/core" "2.0.0-beta.3" - "@docusaurus/theme-common" "2.0.0-beta.3" - "@docusaurus/utils" "2.0.0-beta.3" - "@docusaurus/utils-validation" "2.0.0-beta.3" - algoliasearch "^4.8.4" - algoliasearch-helper "^3.3.4" + fs-extra "^10.1.0" + react-live "2.2.3" + tslib "^2.4.0" + +"@docusaurus/theme-search-algolia@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.20.tgz#14f2ea376a87d7cfa4840d8c917d1ec62d3a07f7" + integrity sha512-9XAyiXXHgyhDmKXg9RUtnC4WBkYAZUqKT9Ntuk0OaOb4mBwiYUGL74tyP0LLL6T+oa9uEdXiUMlIL1onU8xhvA== + dependencies: + "@docsearch/react" "^3.0.0" + "@docusaurus/core" "2.0.0-beta.20" + "@docusaurus/logger" "2.0.0-beta.20" + "@docusaurus/plugin-content-docs" "2.0.0-beta.20" + "@docusaurus/theme-common" "2.0.0-beta.20" + "@docusaurus/theme-translations" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + "@docusaurus/utils-validation" "2.0.0-beta.20" + algoliasearch "^4.13.0" + algoliasearch-helper "^3.8.2" clsx "^1.1.1" - eta "^1.12.1" - lodash "^4.17.20" + eta "^1.12.3" + fs-extra "^10.1.0" + lodash "^4.17.21" + tslib "^2.4.0" + utility-types "^3.10.0" + +"@docusaurus/theme-translations@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.20.tgz#dcc7efb43ff110c19736764c287f6c5128a5dbba" + integrity sha512-O7J/4dHcg7Yr+r3ylgtqmtMEz6d5ScpUxBg8nsNTWOCRoGEXNZVmXSd5l6v72KCyxPZpllPrgjmqkL+I19qWiw== + dependencies: + fs-extra "^10.1.0" + tslib "^2.4.0" -"@docusaurus/types@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-beta.3.tgz#042838d3a1ce0aa6f0df1b87180da0d503268d9b" - integrity sha512-ivQ6L1ahju06ldTvFsZLQxcN6DP32iIB7DscxWVRqP0eyuyX2xAy+jrASqih3lB8lyw0JJaaDEeVE5fjroAQ/Q== +"@docusaurus/types@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-beta.20.tgz#069d40cc225141d5c9a85605a1c61a460814bf5d" + integrity sha512-d4ZIpcrzGsUUcZJL3iz8/iSaewobPPiYfn2Lmmv7GTT5ZPtPkOAtR5mE6+LAf/KpjjgqrC7mpwDKADnOL/ic4Q== dependencies: commander "^5.1.0" - joi "^17.4.0" - querystring "0.2.0" - webpack "^5.40.0" + history "^4.9.0" + joi "^17.6.0" + react-helmet-async "^1.3.0" + utility-types "^3.10.0" + webpack "^5.72.0" webpack-merge "^5.8.0" -"@docusaurus/utils-common@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.3.tgz#85fc7f7572d0f55e62b8f0baeb91967bf7d699e0" - integrity sha512-KJgDN4G2MzJcHy+OR2e/xgEwRy+vX26pzwtjPkRjNf24CPa0BwFbRmR5apbltCgTB10vT6xroStc8Quv/286Cg== +"@docusaurus/utils-common@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.20.tgz#adb914c331d711a3c0ef2ba3f64139acdf4cd781" + integrity sha512-HabHh23vOQn6ygs0PjuCSF/oZaNsYTFsxB2R6EwHNyw01nWgBC3QAcGVmyIWQhlb9p8V3byKgbzVS68hZX5t9A== dependencies: - "@docusaurus/types" "2.0.0-beta.3" - tslib "^2.2.0" + tslib "^2.4.0" -"@docusaurus/utils-validation@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.3.tgz#24e8187ff853dfec111faaef75af432274bd8773" - integrity sha512-jGX78NNrxDZFgDjLaa6wuJ/eKDoHdZFG2CVX3uCaIGe1x8eTMG2/e/39GzbZl+W7VHYpW0bzdf/5dFhaKLfQbQ== +"@docusaurus/utils-validation@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.20.tgz#ebf475a4388066bd450877fe920f405c53ff5ad2" + integrity sha512-7MxMoaF4VNAt5vUwvITa6nbkw1tb4WE6hp1VlfIoLCY4D7Wk5cMf1ZFhppCP1UzmPwvFb9zw8fPuvDfB3Tb5nQ== dependencies: - "@docusaurus/utils" "2.0.0-beta.3" - chalk "^4.1.1" - joi "^17.4.0" - tslib "^2.1.0" + "@docusaurus/logger" "2.0.0-beta.20" + "@docusaurus/utils" "2.0.0-beta.20" + joi "^17.6.0" + js-yaml "^4.1.0" + tslib "^2.4.0" -"@docusaurus/utils@2.0.0-beta.3": - version "2.0.0-beta.3" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.3.tgz#81bdf02c5128f3d307d56925cbc398dbf3600e50" - integrity sha512-DApc6xcb3CvvsBCfRU6Zk3KoZa4mZfCJA4XRv5zhlhaSb0GFuAo7KQ353RUu6d0eYYylY3GGRABXkxRE1SEClA== +"@docusaurus/utils@2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.20.tgz#d5a9816a328b2ca5e4e1a3fbf267e3674abacd48" + integrity sha512-eUQquakhrbnvhsmx8jRPLgoyjyzMuOhmQC99m7rotar7XOzROpgEpm7+xVaquG5Ha47WkybE3djHJhKNih7GZQ== dependencies: - "@docusaurus/types" "2.0.0-beta.3" - "@types/github-slugger" "^1.3.0" - chalk "^4.1.1" - escape-string-regexp "^4.0.0" - fs-extra "^10.0.0" + "@docusaurus/logger" "2.0.0-beta.20" + "@svgr/webpack" "^6.2.1" + file-loader "^6.2.0" + fs-extra "^10.1.0" + github-slugger "^1.4.0" + globby "^11.1.0" gray-matter "^4.0.3" - lodash "^4.17.20" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" resolve-pathname "^3.0.0" - tslib "^2.2.0" + shelljs "^0.8.5" + tslib "^2.4.0" + url-loader "^4.1.1" + webpack "^5.72.0" "@emotion/cache@^10.0.27", "@emotion/cache@^10.0.9": version "10.0.29" @@ -2090,9 +2168,9 @@ integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== "@hapi/hoek@^9.0.0": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" - integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw== + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== "@hapi/topo@^5.0.0": version "5.1.0" @@ -2126,7 +2204,7 @@ js-yaml "^3.13.1" resolve-from "^5.0.0" -"@istanbuljs/schema@^0.1.2": +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== @@ -2296,138 +2374,95 @@ resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^13.0.0" - -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@jimp/bmp@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.16.1.tgz#6e2da655b2ba22e721df0795423f34e92ef13768" - integrity sha512-iwyNYQeBawrdg/f24x3pQ5rEx+/GwjZcCXd3Kgc+ZUd+Ivia7sIqBsOnDaMZdKCBPlfW364ekexnlOqyVa0NWg== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.16.1" - bmp-js "^0.1.0" - -"@jimp/core@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.16.1.tgz#68c4288f6ef7f31a0f6b859ba3fb28dae930d39d" - integrity sha512-la7kQia31V6kQ4q1kI/uLimu8FXx7imWVajDGtwUG8fzePLWDFJyZl0fdIXVCL1JW2nBcRHidUot6jvlRDi2+g== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.16.1" - any-base "^1.1.0" - buffer "^5.2.0" - exif-parser "^0.1.12" - file-type "^9.0.0" - load-bmfont "^1.3.1" - mkdirp "^0.5.1" - phin "^2.9.1" - pixelmatch "^4.0.2" - tinycolor2 "^1.4.1" - -"@jimp/custom@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.16.1.tgz#28b659c59e20a1d75a0c46067bd3f4bd302cf9c5" - integrity sha512-DNUAHNSiUI/j9hmbatD6WN/EBIyeq4AO0frl5ETtt51VN1SvE4t4v83ZA/V6ikxEf3hxLju4tQ5Pc3zmZkN/3A== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/core" "^0.16.1" - -"@jimp/gif@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.16.1.tgz#d1f7c3a58f4666482750933af8b8f4666414f3ca" - integrity sha512-r/1+GzIW1D5zrP4tNrfW+3y4vqD935WBXSc8X/wm23QTY9aJO9Lw6PEdzpYCEY+SOklIFKaJYUAq/Nvgm/9ryw== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.16.1" - gifwrap "^0.9.2" - omggif "^1.0.9" - -"@jimp/jpeg@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.16.1.tgz#3b7bb08a4173f2f6d81f3049b251df3ee2ac8175" - integrity sha512-8352zrdlCCLFdZ/J+JjBslDvml+fS3Z8gttdml0We759PnnZGqrnPRhkOEOJbNUlE+dD4ckLeIe6NPxlS/7U+w== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.16.1" - jpeg-js "0.4.2" - -"@jimp/plugin-resize@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.16.1.tgz#65e39d848ed13ba2d6c6faf81d5d590396571d10" - integrity sha512-u4JBLdRI7dargC04p2Ha24kofQBk3vhaf0q8FwSYgnCRwxfvh2RxvhJZk9H7Q91JZp6wgjz/SjvEAYjGCEgAwQ== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.16.1" - -"@jimp/png@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.16.1.tgz#f24cfc31529900b13a2dd9d4fdb4460c1e4d814e" - integrity sha512-iyWoCxEBTW0OUWWn6SveD4LePW89kO7ZOy5sCfYeDM/oTPLpR8iMIGvZpZUz1b8kvzFr27vPst4E5rJhGjwsdw== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.16.1" - pngjs "^3.3.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^13.0.0" -"@jimp/tiff@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.16.1.tgz#0e8756695687d7574b6bc73efab0acd4260b7a12" - integrity sha512-3K3+xpJS79RmSkAvFMgqY5dhSB+/sxhwTFA9f4AVHUK0oKW+u6r52Z1L0tMXHnpbAdR9EJ+xaAl2D4x19XShkQ== +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== dependencies: - "@babel/runtime" "^7.7.2" - utif "^2.0.1" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" -"@jimp/types@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.16.1.tgz#0dbab37b3202315c91010f16c31766d35a2322cc" - integrity sha512-g1w/+NfWqiVW4CaXSJyD28JQqZtm2eyKMWPhBBDCJN9nLCN12/Az0WFF3JUAktzdsEC2KRN2AqB1a2oMZBNgSQ== +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/bmp" "^0.16.1" - "@jimp/gif" "^0.16.1" - "@jimp/jpeg" "^0.16.1" - "@jimp/png" "^0.16.1" - "@jimp/tiff" "^0.16.1" - timm "^1.6.1" - -"@jimp/utils@^0.16.1": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.16.1.tgz#2f51e6f14ff8307c4aa83d5e1a277da14a9fe3f7" - integrity sha512-8fULQjB0x4LzUSiSYG6ZtQl355sZjxbv8r9PPAuYHzS9sGiSHJQavNqK/nKnpDsVkU88/vRGcE7t3nMU0dEnVw== + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== dependencies: - "@babel/runtime" "^7.7.2" - regenerator-runtime "^0.13.3" + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" - integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/set-array@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== -"@jridgewell/trace-mapping@^0.3.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" - integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== +"@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" + integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@kitware/vtk.js@^24.18.7": + version "24.18.7" + resolved "https://registry.yarnpkg.com/@kitware/vtk.js/-/vtk.js-24.18.7.tgz#746cb26e4c98e86f70eb51772d2407432f53e672" + integrity sha512-I40IUDZ9menwJtOvYKsp+FoHWTlkk7JYf2BcBCq87ayEC4hd1+wTEhXq38LjwEvCeALEaFnjrtYYXvaKt8Nwtg== + dependencies: + "@babel/runtime" "7.17.9" + commander "9.2.0" + d3-scale "4.0.2" + gl-matrix "3.4.3" + globalthis "1.0.3" + jszip "3.9.1" + pako "2.0.4" + seedrandom "3.0.5" + shader-loader "1.3.1" + shelljs "0.8.5" + spark-md5 "^3.0.2" + stream-browserify "3.0.0" + webworker-promise "0.5.0" + worker-loader "3.0.8" + xmlbuilder2 "3.0.2" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + "@lerna/add@3.21.0": version "3.21.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b" @@ -3113,16 +3148,7 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" -"@mdx-js/loader@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.6.22.tgz#d9e8fe7f8185ff13c9c8639c048b123e30d322c4" - integrity sha512-9CjGwy595NaxAYp0hF9B/A0lH6C8Rms97e2JS9d3jVUtILn6pT5i5IV965ra3lIWc7Rs1GG1tBdVF7dCowYe6Q== - dependencies: - "@mdx-js/mdx" "1.6.22" - "@mdx-js/react" "1.6.22" - loader-utils "2.0.0" - -"@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.21", "@mdx-js/mdx@^1.6.22": +"@mdx-js/mdx@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== @@ -3147,7 +3173,7 @@ unist-builder "2.0.3" unist-util-visit "2.0.3" -"@mdx-js/react@1.6.22", "@mdx-js/react@^1.6.21", "@mdx-js/react@^1.6.22": +"@mdx-js/react@^1.6.21", "@mdx-js/react@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== @@ -3157,6 +3183,21 @@ resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== +"@microsoft/tsdoc-config@0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz#4de11976c1202854c4618f364bf499b4be33e657" + integrity sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ== + dependencies: + "@microsoft/tsdoc" "0.14.1" + ajv "~6.12.6" + jju "~1.4.0" + resolve "~1.19.0" + +"@microsoft/tsdoc@0.14.1": + version "0.14.1" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz#155ef21065427901994e765da8a0ba0eaae8b8bd" + integrity sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -3207,164 +3248,6 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@oclif/color@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@oclif/color/-/color-0.1.2.tgz#28b07e2850d9ce814d0b587ce3403b7ad8f7d987" - integrity sha512-M9o+DOrb8l603qvgz1FogJBUGLqcMFL1aFg2ZEL0FbXJofiNTLOWIeB4faeZTLwE6dt0xH9GpCVpzksMMzGbmA== - dependencies: - ansi-styles "^3.2.1" - chalk "^3.0.0" - strip-ansi "^5.2.0" - supports-color "^5.4.0" - tslib "^1" - -"@oclif/command@1.5.19": - version "1.5.19" - resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.5.19.tgz#13f472450eb83bd6c6871a164c03eadb5e1a07ed" - integrity sha512-6+iaCMh/JXJaB2QWikqvGE9//wLEVYYwZd5sud8aLoLKog1Q75naZh2vlGVtg5Mq/NqpqGQvdIjJb3Bm+64AUQ== - dependencies: - "@oclif/config" "^1" - "@oclif/errors" "^1.2.2" - "@oclif/parser" "^3.8.3" - "@oclif/plugin-help" "^2" - debug "^4.1.1" - semver "^5.6.0" - -"@oclif/command@1.8.11": - version "1.8.11" - resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.11.tgz#926919fe8ddb7ab778fef8a8f2951c975f35e0c2" - integrity sha512-2fGLMvi6J5+oNxTaZfdWPMWY8oW15rYj0V8yLzmZBAEjfzjLqLIzJE9IlNccN1zwRqRHc1bcISSRDdxJ56IS/Q== - dependencies: - "@oclif/config" "^1.18.2" - "@oclif/errors" "^1.3.5" - "@oclif/parser" "^3.8.6" - "@oclif/plugin-help" "3.2.14" - debug "^4.1.1" - semver "^7.3.2" - -"@oclif/command@^1.5.13", "@oclif/command@^1.8.9": - version "1.8.16" - resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.16.tgz#bea46f81b2061b47e1cda318a0b923e62ca4cc0c" - integrity sha512-rmVKYEsKzurfRU0xJz+iHelbi1LGlihIWZ7Qvmb/CBz1EkhL7nOkW4SVXmG2dA5Ce0si2gr88i6q4eBOMRNJ1w== - dependencies: - "@oclif/config" "^1.18.2" - "@oclif/errors" "^1.3.5" - "@oclif/help" "^1.0.1" - "@oclif/parser" "^3.8.6" - debug "^4.1.1" - semver "^7.3.2" - -"@oclif/config@1.18.2": - version "1.18.2" - resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.2.tgz#5bfe74a9ba6a8ca3dceb314a81bd9ce2e15ebbfe" - integrity sha512-cE3qfHWv8hGRCP31j7fIS7BfCflm/BNZ2HNqHexH+fDrdF2f1D5S8VmXWLC77ffv3oDvWyvE9AZeR0RfmHCCaA== - dependencies: - "@oclif/errors" "^1.3.3" - "@oclif/parser" "^3.8.0" - debug "^4.1.1" - globby "^11.0.1" - is-wsl "^2.1.1" - tslib "^2.0.0" - -"@oclif/config@^1", "@oclif/config@^1.18.2": - version "1.18.3" - resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.18.3.tgz#ddfc144fdab66b1658c2f1b3478fa7fbfd317e79" - integrity sha512-sBpko86IrTscc39EvHUhL+c++81BVTsIZ3ETu/vG+cCdi0N6vb2DoahR67A9FI2CGnxRRHjnTfa3m6LulwNATA== - dependencies: - "@oclif/errors" "^1.3.5" - "@oclif/parser" "^3.8.0" - debug "^4.1.1" - globby "^11.0.1" - is-wsl "^2.1.1" - tslib "^2.3.1" - -"@oclif/errors@1.3.5", "@oclif/errors@^1.2.2", "@oclif/errors@^1.3.3", "@oclif/errors@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.5.tgz#a1e9694dbeccab10fe2fe15acb7113991bed636c" - integrity sha512-OivucXPH/eLLlOT7FkCMoZXiaVYf8I/w1eTAM1+gKzfhALwWTusxEx7wBmW0uzvkSg/9ovWLycPaBgJbM3LOCQ== - dependencies: - clean-stack "^3.0.0" - fs-extra "^8.1" - indent-string "^4.0.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -"@oclif/help@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@oclif/help/-/help-1.0.1.tgz#fd96a3dd9fb2314479e6c8584c91b63754a7dff5" - integrity sha512-8rsl4RHL5+vBUAKBL6PFI3mj58hjPCp2VYyXD4TAa7IMStikFfOH2gtWmqLzIlxAED2EpD0dfYwo9JJxYsH7Aw== - dependencies: - "@oclif/config" "1.18.2" - "@oclif/errors" "1.3.5" - chalk "^4.1.2" - indent-string "^4.0.0" - lodash "^4.17.21" - string-width "^4.2.0" - strip-ansi "^6.0.0" - widest-line "^3.1.0" - wrap-ansi "^6.2.0" - -"@oclif/linewrap@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91" - integrity sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw== - -"@oclif/parser@^3.8.0", "@oclif/parser@^3.8.3", "@oclif/parser@^3.8.6": - version "3.8.7" - resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.8.7.tgz#236d48db05d0b00157d3b42d31f9dac7550d2a7c" - integrity sha512-b11xBmIUK+LuuwVGJpFs4LwQN2xj2cBWj2c4z1FtiXGrJ85h9xV6q+k136Hw0tGg1jQoRXuvuBnqQ7es7vO9/Q== - dependencies: - "@oclif/errors" "^1.3.5" - "@oclif/linewrap" "^1.0.0" - chalk "^4.1.0" - tslib "^2.3.1" - -"@oclif/plugin-help@3.2.14": - version "3.2.14" - resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.14.tgz#7149eb322d36abc6cbf09f205bad128141e7eba4" - integrity sha512-NP5qmE2YfcW3MmXjcrxiqKe9Hf3G0uK/qNc0zAMYKU4crFyIsWj7dBfQVFZSb28YXGioOOpjMzG1I7VMxKF38Q== - dependencies: - "@oclif/command" "^1.8.9" - "@oclif/config" "^1.18.2" - "@oclif/errors" "^1.3.5" - chalk "^4.1.2" - indent-string "^4.0.0" - lodash "^4.17.21" - string-width "^4.2.0" - strip-ansi "^6.0.0" - widest-line "^3.1.0" - wrap-ansi "^6.2.0" - -"@oclif/plugin-help@^2": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-2.2.3.tgz#b993041e92047f0e1762668aab04d6738ac06767" - integrity sha512-bGHUdo5e7DjPJ0vTeRBMIrfqTRDBfyR5w0MP41u0n3r7YG5p14lvMmiCXxi6WDaP2Hw5nqx3PnkAIntCKZZN7g== - dependencies: - "@oclif/command" "^1.5.13" - chalk "^2.4.1" - indent-string "^4.0.0" - lodash.template "^4.4.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - widest-line "^2.0.1" - wrap-ansi "^4.0.0" - -"@oclif/plugin-not-found@^1.2": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@oclif/plugin-not-found/-/plugin-not-found-1.2.6.tgz#7e354f7de6866477fe415c8fde357765876fe8ec" - integrity sha512-cfkDub79I9EpselfU/W8FTXhslrkOgfqjaa25tyGo99dAX5UVr6BWL2wbUobsU+rUcm4HN3byzdHDcqfu6hoAw== - dependencies: - "@oclif/color" "^0.1.2" - "@oclif/command" "1.8.11" - cli-ux "5.6.6" - fast-levenshtein "^3.0.0" - lodash "^4.17.21" - -"@oclif/screen@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493" - integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw== - "@octokit/auth-token@^2.4.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" @@ -3477,41 +3360,46 @@ dependencies: "@octokit/openapi-types" "^11.2.0" -"@percy/agent@~0": - version "0.28.7" - resolved "https://registry.yarnpkg.com/@percy/agent/-/agent-0.28.7.tgz#babacf424e7e011b0c79a760ecf9a555761521b0" - integrity sha512-mJTaoc6ocxetfMcxFMKzBB23N5gxf/v8v1AT4WK59AibP4KHDRSr+j67q6GkrRjYeEJuWEZGwUrE6Kx2hOi8MA== +"@oozcitak/dom@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@oozcitak/dom/-/dom-1.15.10.tgz#dca7289f2b292cff2a901ea4fbbcc0a1ab0b05c2" + integrity sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ== dependencies: - "@oclif/command" "1.5.19" - "@oclif/config" "^1" - "@oclif/plugin-help" "^2" - "@oclif/plugin-not-found" "^1.2" - axios "^0.21.1" - body-parser "^1.18.3" - colors "1.4.0" - cors "^2.8.4" - cosmiconfig "^5.2.1" - cross-spawn "^7.0.2" - deepmerge "^4.0.0" - express "^4.16.3" - follow-redirects "1.12.1" - generic-pool "^3.7.1" - globby "^10.0.1" - image-size "^0.8.2" - js-yaml "^3.13.1" - percy-client "^3.2.0" - puppeteer "^5.3.1" - retry-axios "^1.0.1" - which "^2.0.1" - winston "^3.0.0" + "@oozcitak/infra" "1.0.8" + "@oozcitak/url" "1.0.4" + "@oozcitak/util" "8.3.8" -"@percy/cypress@^2.3.0": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@percy/cypress/-/cypress-2.3.4.tgz#9605fd848ab27aab795515a22a8868078ba57838" - integrity sha512-UiVnLdcgRnOD1C5orkQiCQC1OFcb/Kt4CWSOVg61JkWNgeWb03os9HN2vjlwgb11eCR8gLHBs+O7T9MRMy28Qg== +"@oozcitak/infra@1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@oozcitak/infra/-/infra-1.0.8.tgz#b0b089421f7d0f6878687608301fbaba837a7d17" + integrity sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg== + dependencies: + "@oozcitak/util" "8.3.8" + +"@oozcitak/url@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@oozcitak/url/-/url-1.0.4.tgz#ca8b1c876319cf5a648dfa1123600a6aa5cda6ba" + integrity sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw== + dependencies: + "@oozcitak/infra" "1.0.8" + "@oozcitak/util" "8.3.8" + +"@oozcitak/util@8.3.8": + version "8.3.8" + resolved "https://registry.yarnpkg.com/@oozcitak/util/-/util-8.3.8.tgz#10f65fe1891fd8cde4957360835e78fd1936bfdd" + integrity sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ== + +"@percy/cypress@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@percy/cypress/-/cypress-3.1.1.tgz#4e7c5bdeccf1240b2150fc9d608df72c2f213d4b" + integrity sha512-khvWmCOJW7pxwDZPB5ovvbSe11FfNtH8Iyq8PHRYLD9ibAkiAWHZVs07bLK5wju1Q9X8s7zg5uj2yWxIlB1yjA== dependencies: - "@percy/agent" "~0" - axios "^0.21.0" + "@percy/sdk-utils" "^1.0.0-beta.44" + +"@percy/sdk-utils@^1.0.0-beta.44": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@percy/sdk-utils/-/sdk-utils-1.2.1.tgz#fdb29b400ad45c1c4998716587a80475e790e1b9" + integrity sha512-Ey7T4XNJHb1kolMTFyh322tn9hapeLChCUovMy3P9zQ+Fh/LtyspVNo8uhOG+Vmidxqb0bwXkooTQTgGODFckQ== "@philpl/buble@^0.19.7": version "0.19.7" @@ -3528,10 +3416,10 @@ os-homedir "^1.0.1" regexpu-core "^4.5.4" -"@pmmmwh/react-refresh-webpack-plugin@^0.5.1": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.5.tgz#e77aac783bd079f548daa0a7f080ab5b5a9741ca" - integrity sha512-RbG7h6TuP6nFFYKJwbcToA1rjC1FyPg25NR2noAZ0vKI+la01KTSRPkuVPE+U88jXv7javx2JHglUcL1MHcshQ== +"@pmmmwh/react-refresh-webpack-plugin@^0.5.3": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz#58f8217ba70069cc6a73f5d7e05e85b458c150e2" + integrity sha512-bcKCAzF0DV2IIROp9ZHkRJa6O4jy7NlnHdWL3GmcUxYWNjLXkK5kfELELwEfSP5hXPfVL/qOGMAROuMQb9GG8Q== dependencies: ansi-html-community "^0.0.8" common-path-prefix "^3.0.0" @@ -3548,11 +3436,6 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== -"@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0": - version "2.11.5" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64" - integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw== - "@reach/router@^1.3.4": version "1.3.4" resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c" @@ -3644,7 +3527,7 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@slorber/static-site-generator-webpack-plugin@^4.0.0": +"@slorber/static-site-generator-webpack-plugin@^4.0.4": version "4.0.4" resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.4.tgz#2bf4a2545e027830d2aa5eb950437c26a289b0f1" integrity sha512-FvMavoWEIePps6/JwGCOLYKCRhuwIHhMtmbKpBFgzNkxwpa/569LfTkrbRk1m1I3n+ezJK4on9E1A6cjuZmD9g== @@ -3654,42 +3537,43 @@ eval "^0.1.8" webpack-sources "^1.4.3" -"@storybook/addon-actions@6.4.20", "@storybook/addon-actions@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.4.20.tgz#d110b333cab97e3f89dba508f586928bf638aaaf" - integrity sha512-5kW4orA6rOHzrDSvGwGL+uevsK9OzJRXq36eje3hCj+E5TGE8hApi+PIIBXI8bIkeJ3zkAS5kjMFdOk+8moT0g== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/theming" "6.4.20" +"@storybook/addon-actions@6.5.6", "@storybook/addon-actions@^6.4.9": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.5.6.tgz#0e980a0a6d8d47db8031108ffa1fd51cc627a466" + integrity sha512-AGtzpWOU/B0FxcqFDM7E/KSHQyr6tMbVts77JlAKCIbwqEncD1LIQoz9CyMdbr1jynkep0Ck0JjcDdmp7CXVoQ== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/theming" "6.5.6" core-js "^3.8.2" fast-deep-equal "^3.1.3" global "^4.4.0" lodash "^4.17.21" - polished "^4.0.5" + polished "^4.2.2" prop-types "^15.7.2" react-inspector "^5.1.0" regenerator-runtime "^0.13.7" - telejson "^5.3.2" + telejson "^6.0.8" ts-dedent "^2.0.0" util-deprecate "^1.0.2" uuid-browser "^3.1.0" -"@storybook/addon-backgrounds@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.4.20.tgz#57db1fb935e6c069cbb17c8052a12fdd38e99f76" - integrity sha512-7zjCJSrnhq1xtyChpwjtYOdrDKxxD7Rs82qF38p8qMAzSvKBNhm3dK8C+iWHt7pu4+cwMpXou1cvWJJVx+qGvA== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/theming" "6.4.20" +"@storybook/addon-backgrounds@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.5.6.tgz#3763acdff1f2260286e165cf35e87d8097245496" + integrity sha512-D2khx57WBvQ2Ta/UMCZa8KQwAzVunk/oKmTJd6vau+1bm88wEx3a2seI9tZtC6nVHgHaBYgoH8os+7XpmlYlVQ== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/theming" "6.5.6" core-js "^3.8.2" global "^4.4.0" memoizerific "^1.11.3" @@ -3697,70 +3581,52 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/addon-controls@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.4.20.tgz#62f63621001616a094d69f1da42d003708a286a6" - integrity sha512-Tqq66SCbi2WIiKrkHu3edtg4r8QIdm/RbNB/PwnFuXwkJVt5mAoV9QQUt1zkbzdknU8xTwwgM4cEEfYLfBVm9Q== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-common" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/node-logger" "6.4.20" - "@storybook/store" "6.4.20" - "@storybook/theming" "6.4.20" +"@storybook/addon-controls@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.5.6.tgz#bca1e33dd1edb059b6d6b1e1616c60ecdc9eb294" + integrity sha512-pJz2ltdL8d9/2wX1XOMdXeGwMNoBX5hIXkNfBCvBfOCmeVEXOczPOZARvwx4QTqmMSu1KikhFIGT64wwoETmBg== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/node-logger" "6.5.6" + "@storybook/store" "6.5.6" + "@storybook/theming" "6.5.6" core-js "^3.8.2" lodash "^4.17.21" ts-dedent "^2.0.0" -"@storybook/addon-docs@6.4.20", "@storybook/addon-docs@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.4.20.tgz#ccd70a8c9811d90d6259f012b604b42f4a4de806" - integrity sha512-Rz001irN1TRKLNKVhvNNSGVWRnFHJxOaRHDbY+4dr8kPCLKM+Abd2lGvj1VdxFo6/sB7H01ihc+ofm6fIv4T3w== +"@storybook/addon-docs@6.5.6", "@storybook/addon-docs@^6.4.9": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.5.6.tgz#39ea88cf5feebc82cc7faf9c0a10f9f3cba420bb" + integrity sha512-18MOB4Cvr10ibRlA58Y2MqaC0EM9NG758iSjweThaU4kZtSBSDn8R2qBLDGQPwEFkww+4+oAFXxR5/J0qO2xEw== dependencies: - "@babel/core" "^7.12.10" - "@babel/generator" "^7.12.11" - "@babel/parser" "^7.12.11" "@babel/plugin-transform-react-jsx" "^7.12.12" "@babel/preset-env" "^7.12.11" "@jest/transform" "^26.6.2" - "@mdx-js/loader" "^1.6.22" - "@mdx-js/mdx" "^1.6.22" "@mdx-js/react" "^1.6.22" - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/builder-webpack4" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/csf-tools" "6.4.20" - "@storybook/node-logger" "6.4.20" - "@storybook/postinstall" "6.4.20" - "@storybook/preview-web" "6.4.20" - "@storybook/source-loader" "6.4.20" - "@storybook/store" "6.4.20" - "@storybook/theming" "6.4.20" - acorn "^7.4.1" - acorn-jsx "^5.3.1" - acorn-walk "^7.2.0" + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/docs-tools" "6.5.6" + "@storybook/mdx1-csf" "^0.0.1" + "@storybook/node-logger" "6.5.6" + "@storybook/postinstall" "6.5.6" + "@storybook/preview-web" "6.5.6" + "@storybook/source-loader" "6.5.6" + "@storybook/store" "6.5.6" + "@storybook/theming" "6.5.6" + babel-loader "^8.0.0" core-js "^3.8.2" - doctrine "^3.0.0" - escodegen "^2.0.0" fast-deep-equal "^3.1.3" global "^4.4.0" - html-tags "^3.1.0" - js-string-escape "^1.0.1" - loader-utils "^2.0.0" lodash "^4.17.21" - nanoid "^3.1.23" - p-limit "^3.1.0" - prettier ">=2.2.1 <=2.3.0" - prop-types "^15.7.2" - react-element-to-jsx-string "^14.3.4" regenerator-runtime "^0.13.7" remark-external-links "^8.0.0" remark-slug "^6.0.0" @@ -3768,21 +3634,22 @@ util-deprecate "^1.0.2" "@storybook/addon-essentials@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.4.20.tgz#35a878488c97ef8436215bd273284e9a4c669246" - integrity sha512-BiEICsj4uA5S/qUw7cBImiDB7Q0TNBd2PK3HkhRE7WOd4NxxPPzXwpE4FX/kPmejYo+cIzYPSiISevkdN6cCvw== - dependencies: - "@storybook/addon-actions" "6.4.20" - "@storybook/addon-backgrounds" "6.4.20" - "@storybook/addon-controls" "6.4.20" - "@storybook/addon-docs" "6.4.20" - "@storybook/addon-measure" "6.4.20" - "@storybook/addon-outline" "6.4.20" - "@storybook/addon-toolbars" "6.4.20" - "@storybook/addon-viewport" "6.4.20" - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/node-logger" "6.4.20" + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.5.6.tgz#502e6152b729696d8de8c792fb44aae76fae934e" + integrity sha512-n+kDN/AI6NDJNJjofYXjGBh618Yg17DuRppFdXROnocwndKufuImF+/tRaQ0YLwXmnULpAiFSGbja6F0pc7fOw== + dependencies: + "@storybook/addon-actions" "6.5.6" + "@storybook/addon-backgrounds" "6.5.6" + "@storybook/addon-controls" "6.5.6" + "@storybook/addon-docs" "6.5.6" + "@storybook/addon-measure" "6.5.6" + "@storybook/addon-outline" "6.5.6" + "@storybook/addon-toolbars" "6.5.6" + "@storybook/addon-viewport" "6.5.6" + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/node-logger" "6.5.6" core-js "^3.8.2" regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" @@ -3800,15 +3667,15 @@ regenerator-runtime "^0.13.7" "@storybook/addon-links@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-6.4.20.tgz#7e845a20deece65e7e684433d4c66a6ad61da52c" - integrity sha512-TyRuEd/3yRn2N9xasCKuE2bsY0dTRjAquGeg5WEtvHvr8V6QBLYAC4caXwPxIHSTcRQyO5IYYiVzEJ/+219neA== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.20" + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-6.5.6.tgz#1074d239c3c9c8b7168d20c9cc20b8918e4993a6" + integrity sha512-a4+9HLc6+M9qv5Fvezoc8AJYze8HDfHlsEFvCY1AtQiyVEmxFRNBdAp6w+/teNzihXWY78EKRcGM0jjyt+9mWg== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.6" "@types/qs" "^6.9.5" core-js "^3.8.2" global "^4.4.0" @@ -3817,59 +3684,60 @@ regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" -"@storybook/addon-measure@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-6.4.20.tgz#ed3b23ceca4c8c0f6d1ac6ef6be789c570251d8b" - integrity sha512-Tt2kwXa8OXqJ3cFO2xZKMJSpaoMTM1JuhlOitpHy1tXvuRxmUuJJhohAFubnrS/p0JhIV7AD5G4cJcS0qPteQA== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" +"@storybook/addon-measure@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-6.5.6.tgz#843de6286861fed1761544adecf3c49d2982136c" + integrity sha512-V+KKoCTMVakExVmNiSu/1AHVsOgxUt62wo/Xm2fJvtRz+y/5LFFCLnENcN7/8qDTya8vgaHY7FR69zHvGiR4SA== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" core-js "^3.8.2" global "^4.4.0" -"@storybook/addon-outline@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-6.4.20.tgz#1dfd9db03bcf39884533dc45953563ae12a523a3" - integrity sha512-c/wcoBPySUyjjNP6seaAPbUyGn2oGSLGa6cujbV7yoC3726VM5M15b0ZtWDDJTelO8Hx4D2sPvCAGUl7qvShjg== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" +"@storybook/addon-outline@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-6.5.6.tgz#8fedfc7b86faf3dc552b7ca12972909fd022ee5b" + integrity sha512-/Bgo+yQSDwriAZ7pnO0kzaiyMg77bBgieEhrPpjb+AMlWIBU2j9rZYVdmi0egYy075Hle3E/hLwjG4FT4QIo5A== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" core-js "^3.8.2" global "^4.4.0" regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" -"@storybook/addon-toolbars@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.4.20.tgz#334b33f4affcecf7b9d90a86a11ec0c339b58194" - integrity sha512-oEZT57uqKrZTqBuxyNKx23ZhWVm4ZQHIzG7BdFI9uTeNV+kDgx07cLH5YAoZSzWcdUfgImdsJLN2YfOeLfmfww== +"@storybook/addon-toolbars@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.5.6.tgz#f4f311aaf918e1922d21e9c6f15b06e2cb7ac66c" + integrity sha512-9FvnuvLj8QdtSeY7QEuqJieNckwhIkSdmovaFzeToRzJso3eAmqoo6I6pp444QVVnMzrCl5As+2uAxhgaDLEXg== dependencies: - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/theming" "6.4.20" + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/theming" "6.5.6" core-js "^3.8.2" regenerator-runtime "^0.13.7" -"@storybook/addon-viewport@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.4.20.tgz#5440f1f2ed3a9a0e44e6620f5786010f11374317" - integrity sha512-iDeIg+QX6doDR5rzaxPzG3tEnSD+UWVrcY8euHPLBjrsJkiTMaAf4M86sQjEBhY8xEZ+f//QBt8nT4tqBbR9zA== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/theming" "6.4.20" +"@storybook/addon-viewport@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.5.6.tgz#bc6456b1d2f586da104fdc6b91a2d0355530e730" + integrity sha512-zjBjMLfeqflHW7W7iFL5EvxtGbUg+4j+yHjONnKKpZteUjrX4fsBBMOL56mQsC0bAJEQCb3w2SFFskYBa4CzIw== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/theming" "6.5.6" core-js "^3.8.2" global "^4.4.0" memoizerific "^1.11.3" @@ -3891,18 +3759,18 @@ global "^4.4.0" regenerator-runtime "^0.13.7" -"@storybook/addons@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.4.20.tgz#bbf568b7c4c5a25ef296f285aef0299998ec5933" - integrity sha512-NbsLjDSkE9v2fOr0M7r2hpdYnlYs789ALkXemdTz2y0NUYSPdRfzVVQNXWrgmXivWQRL0aJ3bOjCOc668PPYjg== - dependencies: - "@storybook/api" "6.4.20" - "@storybook/channels" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.20" - "@storybook/theming" "6.4.20" +"@storybook/addons@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.5.6.tgz#ed7ab72ac3a4e2d74e2ba4d10b4a0721d66cd1cd" + integrity sha512-Ktv7Pk4iq+8+6yifXu3bOiC1ii9VXD93mmha7XcrdW+wl8jti9Ll5jLKEN0lAhd+Qupz4R0g9+znpRXk5n0pBg== + dependencies: + "@storybook/api" "6.5.6" + "@storybook/channels" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.6" + "@storybook/theming" "6.5.6" "@types/webpack-env" "^1.16.0" core-js "^3.8.2" global "^4.4.0" @@ -3934,18 +3802,18 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/api@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.20.tgz#65da720985b4b46998a405bddc42c9cef9bad7e4" - integrity sha512-YatZjb8HlJFE9umDzd7aqabn5oXvAculX76pTZWMxm53GROMZVeICGOYtSasJZYlkv9fLx/Gy/ksrKQnA719ig== +"@storybook/api@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.6.tgz#7f7c7bc561b3888033eb7074a8c9ecce9f07cb54" + integrity sha512-GHhjb6mji+R+FWytx7MIOKqca49ZXjvnIPpyp5zXAtgdk2Yn2j8C0MRPO1ovh4nHKWVvx6larmhskRxfUjk06g== dependencies: - "@storybook/channels" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.20" + "@storybook/channels" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.6" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.4.20" + "@storybook/theming" "6.5.6" core-js "^3.8.2" fast-deep-equal "^3.1.3" global "^4.4.0" @@ -3953,58 +3821,36 @@ memoizerific "^1.11.3" regenerator-runtime "^0.13.7" store2 "^2.12.0" - telejson "^5.3.2" + telejson "^6.0.8" ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-webpack4@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.20.tgz#e3b5d6b665fbf5a1ec75b7ef32c4c811897ef20d" - integrity sha512-Lekx2T0P5tLD0Xd2+6t2dicbZ2oTX/lW1bc+Uxz6QROLqh4/H84CTyofVLJYmZUtgnLQee/cqz5JVkpoA72ebA== +"@storybook/builder-webpack4@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.5.6.tgz#49eb7272402ff185ae12ffc679aef4677c48537d" + integrity sha512-/nACQ5SoddCs1geGUKXrrXiYDvYdTVXWXc0L6mXawjYANBeWIkAKFlhRpoXGN/KiFuuExO2+UgNCKlUyD0a51Q== dependencies: "@babel/core" "^7.12.10" - "@babel/plugin-proposal-class-properties" "^7.12.1" - "@babel/plugin-proposal-decorators" "^7.12.12" - "@babel/plugin-proposal-export-default-from" "^7.12.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.12.7" - "@babel/plugin-proposal-private-methods" "^7.12.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.12.1" - "@babel/plugin-transform-block-scoping" "^7.12.12" - "@babel/plugin-transform-classes" "^7.12.1" - "@babel/plugin-transform-destructuring" "^7.12.1" - "@babel/plugin-transform-for-of" "^7.12.1" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-transform-shorthand-properties" "^7.12.1" - "@babel/plugin-transform-spread" "^7.12.1" - "@babel/plugin-transform-template-literals" "^7.12.1" - "@babel/preset-env" "^7.12.11" - "@babel/preset-react" "^7.12.10" - "@babel/preset-typescript" "^7.12.7" - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/channel-postmessage" "6.4.20" - "@storybook/channels" "6.4.20" - "@storybook/client-api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-common" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/node-logger" "6.4.20" - "@storybook/preview-web" "6.4.20" - "@storybook/router" "6.4.20" + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/channel-postmessage" "6.5.6" + "@storybook/channels" "6.5.6" + "@storybook/client-api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/node-logger" "6.5.6" + "@storybook/preview-web" "6.5.6" + "@storybook/router" "6.5.6" "@storybook/semver" "^7.3.2" - "@storybook/store" "6.4.20" - "@storybook/theming" "6.4.20" - "@storybook/ui" "6.4.20" - "@types/node" "^14.0.10" + "@storybook/store" "6.5.6" + "@storybook/theming" "6.5.6" + "@storybook/ui" "6.5.6" + "@types/node" "^14.0.10 || ^16.0.0" "@types/webpack" "^4.41.26" autoprefixer "^9.8.6" babel-loader "^8.0.0" - babel-plugin-macros "^2.8.0" - babel-plugin-polyfill-corejs3 "^0.1.0" case-sensitive-paths-webpack-plugin "^2.3.0" core-js "^3.8.2" css-loader "^3.6.0" @@ -4033,49 +3879,30 @@ webpack-virtual-modules "^0.2.2" "@storybook/builder-webpack5@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-6.4.20.tgz#a7e2253f02baf87acd5c5fc7047064a16c308098" - integrity sha512-VFhXNYfveUjsDlVdtPIq2heqmG2ISES/idg/2o29yK4BvqNLA7ojYttJJl1XKiW5QDwG4WdL/fM8BYMwfN5+zg== + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-6.5.6.tgz#ec72827b3b6999362102eb6d173465ae29a94a9d" + integrity sha512-inR1xh16barDutfoxv8MCTQTztASLpewDsu6YDs87ifYlYfWuS/NlbY290EWj4D8je1LY53Rpbpkorj/49wCeA== dependencies: "@babel/core" "^7.12.10" - "@babel/plugin-proposal-class-properties" "^7.12.1" - "@babel/plugin-proposal-decorators" "^7.12.12" - "@babel/plugin-proposal-export-default-from" "^7.12.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.12.7" - "@babel/plugin-proposal-private-methods" "^7.12.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.12.1" - "@babel/plugin-transform-block-scoping" "^7.12.12" - "@babel/plugin-transform-classes" "^7.12.1" - "@babel/plugin-transform-destructuring" "^7.12.1" - "@babel/plugin-transform-for-of" "^7.12.1" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-transform-shorthand-properties" "^7.12.1" - "@babel/plugin-transform-spread" "^7.12.1" - "@babel/preset-env" "^7.12.11" - "@babel/preset-react" "^7.12.10" - "@babel/preset-typescript" "^7.12.7" - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/channel-postmessage" "6.4.20" - "@storybook/channels" "6.4.20" - "@storybook/client-api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-common" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/node-logger" "6.4.20" - "@storybook/preview-web" "6.4.20" - "@storybook/router" "6.4.20" + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/channel-postmessage" "6.5.6" + "@storybook/channels" "6.5.6" + "@storybook/client-api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/node-logger" "6.5.6" + "@storybook/preview-web" "6.5.6" + "@storybook/router" "6.5.6" "@storybook/semver" "^7.3.2" - "@storybook/store" "6.4.20" - "@storybook/theming" "6.4.20" - "@types/node" "^14.0.10" + "@storybook/store" "6.5.6" + "@storybook/theming" "6.5.6" + "@types/node" "^14.0.10 || ^16.0.0" babel-loader "^8.0.0" - babel-plugin-macros "^3.0.1" - babel-plugin-polyfill-corejs3 "^0.1.0" + babel-plugin-named-exports-order "^0.0.2" + browser-assert "^1.2.1" case-sensitive-paths-webpack-plugin "^2.3.0" core-js "^3.8.2" css-loader "^5.0.1" @@ -4095,29 +3922,29 @@ webpack-hot-middleware "^2.25.1" webpack-virtual-modules "^0.4.1" -"@storybook/channel-postmessage@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.4.20.tgz#ab7ced6d8c83cdc4ea661e055ac83f7b20c934ba" - integrity sha512-rKgQZ74WZhcpQY8I9SyMMADWbQ2GQopfzvE35qYJl/7mpEggXjY2nSP6PdQ7uIZzUSiwZFQ3tesCT5frEjF/DA== +"@storybook/channel-postmessage@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.5.6.tgz#3cfaef29c9ff62321152924de478058fdfd9d812" + integrity sha512-kyYO84hItSE1SaEI1xpMYqJOM3MJ2Y2WHx1Hxu5prq2T2cIgUGURyNf3+5G0BLTf2XGNEN/7YYv9rHmQ9GUz8g== dependencies: - "@storybook/channels" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" + "@storybook/channels" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" core-js "^3.8.2" global "^4.4.0" qs "^6.10.0" - telejson "^5.3.2" + telejson "^6.0.8" -"@storybook/channel-websocket@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.20.tgz#c05b286523eccd5bf2e610375d38769699d01917" - integrity sha512-PYQAX53oTaY2zmHzd+GuDjRVDg34Z9Igo648qmBmpbUypWj54QmHeAcLMN8/RZpcsmjtj/gGkS8TwHGew4soZA== +"@storybook/channel-websocket@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.5.6.tgz#9d31ef139f6d86b29278452e9977a47fd3cc9706" + integrity sha512-d09LfP4zVYCCeuCBn+R41jINUBzhRt78tKk5e1wEsMklV5p+l8Zoi3oJD/UQ/EgtMcnC1dKi3WfAPyx3U/wdgg== dependencies: - "@storybook/channels" "6.4.20" - "@storybook/client-logger" "6.4.20" + "@storybook/channels" "6.5.6" + "@storybook/client-logger" "6.5.6" core-js "^3.8.2" global "^4.4.0" - telejson "^5.3.2" + telejson "^6.0.8" "@storybook/channels@6.2.9": version "6.2.9" @@ -4128,27 +3955,27 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/channels@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.20.tgz#a5b3a72c0f95ea28d631817f252496d3718b97e6" - integrity sha512-BXvI2/bQIvtQ0LPJCEQwrYm0iMkXD0Pu4WuUGfRCbyqhyw6/VnxOP0x92mvFbtBvjHhyNwk9kZloHyI5zJ3STg== +"@storybook/channels@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.6.tgz#a373127f0dd841dabfc31607e5cdac6245444b6c" + integrity sha512-rS0Dk9fSiMBsPvkiMo9CuvDCoXbGmYKiX/juS3F4u+sedhH+XtH4jFf49lJWoNVAE6eJPFyXATU4p1mm8Z85kQ== dependencies: core-js "^3.8.2" ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/client-api@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.20.tgz#17a24af4bc047f7a6de647b9c1844ab4e40baf83" - integrity sha512-+AKAj+HoW2PVB58bDON+K484CHuywZegKMztoOzOltGP6c02gSf3Y/tiHg2ybRnq2qGNrypGgMKrX401yMEBmg== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/channel-postmessage" "6.4.20" - "@storybook/channels" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/store" "6.4.20" +"@storybook/client-api@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.5.6.tgz#935026e682d41f8f9cd122dccd8a9ae3ebc3b613" + integrity sha512-+PV3eZ0Hr15volHE/ds6pxsoitsHkUuwGMVLUGvX5JuepDlhWOkQzCxPw2BuABJl6x40/w/Pj2DIyTrg37ZThg== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/channel-postmessage" "6.5.6" + "@storybook/channels" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/store" "6.5.6" "@types/qs" "^6.9.5" "@types/webpack-env" "^1.16.0" core-js "^3.8.2" @@ -4171,59 +3998,44 @@ core-js "^3.8.2" global "^4.4.0" -"@storybook/client-logger@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.20.tgz#5a10d86f720c2a8d408aeb61c8d08eb5dcc3833a" - integrity sha512-vbEivQvLQm05tuqSAb4s9RCc82YF1HcAvRneOYUGI7T/wSoijZzauIstKtb3LHEBBYpsELf4hJ3GuE5xZW3wXg== +"@storybook/client-logger@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.6.tgz#6ce56c687dcd7772247ffa3915b44737a62f729c" + integrity sha512-hJzhn5EO0S4fUAZkKzBGNACJfupV9n+7TSIaU4mpVP+/ImS9uPdorAAtyqp5HC6R6vA67POoSgX0+qPgZnWvaw== dependencies: core-js "^3.8.2" global "^4.4.0" -"@storybook/components@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.20.tgz#d063b6a7e70e1be7c8aa79220bb2cd92be8057a1" - integrity sha512-5JN1pqpkvFuwZNF8bKr+BHttmoCoIYL7TOB4tCb/O8Puu5IKXa0fuCGMGVwUNhheR3lKVmV3C+FdEdl1Gt3xXQ== - dependencies: - "@popperjs/core" "^2.6.0" - "@storybook/client-logger" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/theming" "6.4.20" - "@types/color-convert" "^2.0.0" - "@types/overlayscrollbars" "^1.12.0" +"@storybook/components@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.5.6.tgz#83f03967bdd1c509e5d18111b924a8cb0789718a" + integrity sha512-Qh40wMkqWD3ZbGqxqa0pi5JFWazIbcZWbqUakH9zARgLk+LBrpJvPuu7GmQrDUtJTA33H4tD/4ujt6AibnaQwQ== + dependencies: + "@storybook/client-logger" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/theming" "6.5.6" "@types/react-syntax-highlighter" "11.0.5" - color-convert "^2.0.1" core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.21" - markdown-to-jsx "^7.1.3" - memoizerific "^1.11.3" - overlayscrollbars "^1.13.1" - polished "^4.0.5" - prop-types "^15.7.2" - react-colorful "^5.1.2" - react-popper-tooltip "^3.1.1" - react-syntax-highlighter "^13.5.3" - react-textarea-autosize "^8.3.0" + qs "^6.10.0" + react-syntax-highlighter "^15.4.5" regenerator-runtime "^0.13.7" - ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/core-client@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.20.tgz#0620a2ca489ff656c7001da1db795a0e8eb8966c" - integrity sha512-pDaCGMdGD4OmC+YzghTXd86SLHfnX+/3lqprVtWSUzV2SbpCrdr0ySa01jbRmDaZIdA3YXxt+vW0VrMWnQ+20A== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/channel-postmessage" "6.4.20" - "@storybook/channel-websocket" "6.4.20" - "@storybook/client-api" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/preview-web" "6.4.20" - "@storybook/store" "6.4.20" - "@storybook/ui" "6.4.20" +"@storybook/core-client@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.5.6.tgz#d09d144745c6d8a24dd002ae60ffa9b0c500ae54" + integrity sha512-Xmjt95GYYVRp7ra49Y955BLH/FYlOmuLC4aFTGurjmCay7zUqvExxFk9AUKOkyBb1/S/8iQCG59D0ES6YWoMRw== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/channel-postmessage" "6.5.6" + "@storybook/channel-websocket" "6.5.6" + "@storybook/client-api" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/preview-web" "6.5.6" + "@storybook/store" "6.5.6" + "@storybook/ui" "6.5.6" airbnb-js-shims "^2.2.1" ansi-to-html "^0.6.11" core-js "^3.8.2" @@ -4235,10 +4047,10 @@ unfetch "^4.2.0" util-deprecate "^1.0.2" -"@storybook/core-common@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.20.tgz#ceaa509b18abf4af40723a807b9231eaade47060" - integrity sha512-+jSPpMwWvoyDufLKhYslF9N2y/5gqbgE/bPnqy6TZhC1ia+Lr5S4uK60zAT1OpB6kgXWDbo203NP148uMxJ3VA== +"@storybook/core-common@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.5.6.tgz#12c717f2fb9cad77b38e122fb89c15566130798a" + integrity sha512-+k+D9CzyFHNAy59jt2sfKnb/KU/nXO1hvBVaJAhdocjrDMvHtwYuXWWQrWYX3/VGp9wCa9TC0JG1kz+DWSYXaQ== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-proposal-class-properties" "^7.12.1" @@ -4248,6 +4060,7 @@ "@babel/plugin-proposal-object-rest-spread" "^7.12.1" "@babel/plugin-proposal-optional-chaining" "^7.12.7" "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-proposal-private-property-in-object" "^7.12.1" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-transform-arrow-functions" "^7.12.1" "@babel/plugin-transform-block-scoping" "^7.12.12" @@ -4261,9 +4074,9 @@ "@babel/preset-react" "^7.12.10" "@babel/preset-typescript" "^7.12.7" "@babel/register" "^7.12.1" - "@storybook/node-logger" "6.4.20" + "@storybook/node-logger" "6.5.6" "@storybook/semver" "^7.3.2" - "@types/node" "^14.0.10" + "@types/node" "^14.0.10 || ^16.0.0" "@types/pretty-hrtime" "^1.0.0" babel-loader "^8.0.0" babel-plugin-macros "^3.0.1" @@ -4285,7 +4098,7 @@ pretty-hrtime "^1.0.3" resolve-from "^5.0.0" slash "^3.0.0" - telejson "^5.3.2" + telejson "^6.0.8" ts-dedent "^2.0.0" util-deprecate "^1.0.2" webpack "4" @@ -4297,30 +4110,31 @@ dependencies: core-js "^3.8.2" -"@storybook/core-events@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.20.tgz#c442028a048bac38a60aabcacb5b215a1bc24b25" - integrity sha512-POizjsPSA4SeBRKaIMpH/M2Mtw3ZPp1hCdIXTxK+S2M1j2rt3ZvNnG2y4IJM+dYjkL1Qwl3WJusa7qcDCS2+dA== +"@storybook/core-events@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.6.tgz#b4e9150216810621ba00d8d1eb3f9414493d6e61" + integrity sha512-bzktgM1i0QPrayH1ANbKb7nYpehSpi5QHWps2vVQbvtpI/pGlTtpde1e87vfAt74Bvsvd3/9IpQkQKteDODAkA== dependencies: core-js "^3.8.2" -"@storybook/core-server@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.20.tgz#6bdf6dd5d83713034df950a98f7638e23c64171c" - integrity sha512-AqpTjZE3/23IdDN5i6Srky3zdapQKSnHqlibl1mppRscf1IZe6OJJWtCHACpJKJwnOpPV/WxL8oron4mUjvrbA== +"@storybook/core-server@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.5.6.tgz#4fe464847297117089900d5f9cc9acc08d638723" + integrity sha512-65kwbSXsKPl/0BKjGr9RTihv6jYGGIG/prfLscZPtm3u4/Z8ZxCX94rznztxcUatjLlUfKJ8iimizhUOIa0FJA== dependencies: "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-webpack4" "6.4.20" - "@storybook/core-client" "6.4.20" - "@storybook/core-common" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/csf-tools" "6.4.20" - "@storybook/manager-webpack4" "6.4.20" - "@storybook/node-logger" "6.4.20" + "@storybook/builder-webpack4" "6.5.6" + "@storybook/core-client" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/csf-tools" "6.5.6" + "@storybook/manager-webpack4" "6.5.6" + "@storybook/node-logger" "6.5.6" "@storybook/semver" "^7.3.2" - "@storybook/store" "6.4.20" - "@types/node" "^14.0.10" + "@storybook/store" "6.5.6" + "@storybook/telemetry" "6.5.6" + "@types/node" "^14.0.10 || ^16.0.0" "@types/node-fetch" "^2.5.7" "@types/pretty-hrtime" "^1.0.0" "@types/webpack" "^4.41.26" @@ -4334,36 +4148,38 @@ cpy "^8.1.2" detect-port "^1.3.0" express "^4.17.1" - file-system-cache "^1.0.5" fs-extra "^9.0.1" + global "^4.4.0" globby "^11.0.2" - ip "^1.1.5" + ip "^2.0.0" lodash "^4.17.21" - node-fetch "^2.6.1" + node-fetch "^2.6.7" + open "^8.4.0" pretty-hrtime "^1.0.3" prompts "^2.4.0" regenerator-runtime "^0.13.7" serve-favicon "^2.5.0" slash "^3.0.0" - telejson "^5.3.3" + telejson "^6.0.8" ts-dedent "^2.0.0" util-deprecate "^1.0.2" watchpack "^2.2.0" webpack "4" ws "^8.2.3" + x-default-browser "^0.4.0" -"@storybook/core@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.4.20.tgz#c025bd2b325ca3e432de5c1023ef0a8cc4378f3b" - integrity sha512-CQ3aaTHoHVV9BRUjqdr33cKv+/q1DMWBrtvEuZpW6gKq/CUuDXLQrAUARD18H/I5BlIJGbP5ccwkZNiY34QWKg== +"@storybook/core@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.5.6.tgz#bc1792154ea010500950f0d43c78ae554ea4f9ec" + integrity sha512-DS6Q8SrEXBDoDS2K865NoWggSXEg8L9p+jx8sILLkLrr2QXJT0x6YIFSwEh6rGwkahxDV5ikON/rW39Wlxzk1w== dependencies: - "@storybook/core-client" "6.4.20" - "@storybook/core-server" "6.4.20" + "@storybook/core-client" "6.5.6" + "@storybook/core-server" "6.5.6" -"@storybook/csf-tools@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.4.20.tgz#5b185ac7adda4803f3129f15faef16f2f10c7f74" - integrity sha512-RM/VN7Tt6FVSlDwAEe6fHCJuv3coeupnqhq+K7tjomTCrcoa1Lk6RX9H0Qk50uSoQZCOgRBjL682yBs27VzUbw== +"@storybook/csf-tools@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.5.6.tgz#147ced43313ee217c41b00d5fe32a80cb29b03bd" + integrity sha512-Gfah+5mEUoVG7v+E23svRjKAh546KCPIcwAvGU3m26j3hNbpvKq8edKDr+CCMfehG8VEGSZWfZPsgX04c/ItcA== dependencies: "@babel/core" "^7.12.10" "@babel/generator" "^7.12.11" @@ -4372,14 +4188,11 @@ "@babel/preset-env" "^7.12.11" "@babel/traverse" "^7.12.11" "@babel/types" "^7.12.11" - "@mdx-js/mdx" "^1.6.22" - "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/mdx1-csf" "^0.0.1" core-js "^3.8.2" fs-extra "^9.0.1" global "^4.4.0" - js-string-escape "^1.0.1" - lodash "^4.17.21" - prettier ">=2.2.1 <=2.3.0" regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" @@ -4390,28 +4203,41 @@ dependencies: lodash "^4.17.15" -"@storybook/csf@0.0.2--canary.87bc651.0": - version "0.0.2--canary.87bc651.0" - resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.87bc651.0.tgz#c7b99b3a344117ef67b10137b6477a3d2750cf44" - integrity sha512-ajk1Uxa+rBpFQHKrCcTmJyQBXZ5slfwHVEaKlkuFaW77it8RgbPJp/ccna3sgoi8oZ7FkkOyvv1Ve4SmwFqRqw== +"@storybook/csf@0.0.2--canary.4566f4d.1": + version "0.0.2--canary.4566f4d.1" + resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz#dac52a21c40ef198554e71fe4d20d61e17f65327" + integrity sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ== dependencies: lodash "^4.17.15" -"@storybook/manager-webpack4@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.4.20.tgz#8e896c55a225271c63f948e9a524943036c0c888" - integrity sha512-4Q9ZJNT64Omn0shD8JfXi1yccjQVWruBxKoELbn4zLOUtmb5/ETmBHkek/nBnLo7i5J6ZkyB66L9qokfC/WsxQ== +"@storybook/docs-tools@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-6.5.6.tgz#cf27e06ce25c55327048662f833628d2e329a140" + integrity sha512-QwOFWVzce4m5fQaC39rSUBVzNplpcSfGyIN3rBBen4wHllj43y7i30A69jSW24M15CKdcJmfDaLwN00s7y3ZHQ== + dependencies: + "@babel/core" "^7.12.10" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/store" "6.5.6" + core-js "^3.8.2" + doctrine "^3.0.0" + lodash "^4.17.21" + regenerator-runtime "^0.13.7" + +"@storybook/manager-webpack4@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.5.6.tgz#3a356ee52cb972533a98e4da426bc6cc3b8abaf3" + integrity sha512-GaUT1bNmGebq8Ci52M07XF0Zn9Ak7L8ZaKn8rdBJ4VSPhg0vEAeo7trD3aur3+h/3gLQmK64LhiNSOfvZfQDAw== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-transform-template-literals" "^7.12.1" "@babel/preset-react" "^7.12.10" - "@storybook/addons" "6.4.20" - "@storybook/core-client" "6.4.20" - "@storybook/core-common" "6.4.20" - "@storybook/node-logger" "6.4.20" - "@storybook/theming" "6.4.20" - "@storybook/ui" "6.4.20" - "@types/node" "^14.0.10" + "@storybook/addons" "6.5.6" + "@storybook/core-client" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/node-logger" "6.5.6" + "@storybook/theming" "6.5.6" + "@storybook/ui" "6.5.6" + "@types/node" "^14.0.10 || ^16.0.0" "@types/webpack" "^4.41.26" babel-loader "^8.0.0" case-sensitive-paths-webpack-plugin "^2.3.0" @@ -4420,17 +4246,16 @@ css-loader "^3.6.0" express "^4.17.1" file-loader "^6.2.0" - file-system-cache "^1.0.5" find-up "^5.0.0" fs-extra "^9.0.1" html-webpack-plugin "^4.0.0" - node-fetch "^2.6.1" + node-fetch "^2.6.7" pnp-webpack-plugin "1.6.4" read-pkg-up "^7.0.1" regenerator-runtime "^0.13.7" resolve-from "^5.0.0" style-loader "^1.3.0" - telejson "^5.3.2" + telejson "^6.0.8" terser-webpack-plugin "^4.2.3" ts-dedent "^2.0.0" url-loader "^4.1.1" @@ -4440,37 +4265,36 @@ webpack-virtual-modules "^0.2.2" "@storybook/manager-webpack5@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/manager-webpack5/-/manager-webpack5-6.4.20.tgz#90477b971be4a393c51b1910db23844558b5b624" - integrity sha512-I6oviMt5eqBrDPgQ/AcJ6G3/oqBENnZnBIh0X1SP5EtxHy9pEPJcUbfp2NdoeQDN8MAJcGQFbus0CzP2B+bg/A== + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/manager-webpack5/-/manager-webpack5-6.5.6.tgz#7357d97f91e60810ea866f099aa1b82b5afc0f73" + integrity sha512-U5OIMs1PNjZZRjOvV2Bo05+1dzvV30o4Lw5sgeyH9Qr08p79nHT4iFSaVF2W01zGja6XlneqFB9hBqBHoocJWQ== dependencies: "@babel/core" "^7.12.10" "@babel/plugin-transform-template-literals" "^7.12.1" "@babel/preset-react" "^7.12.10" - "@storybook/addons" "6.4.20" - "@storybook/core-client" "6.4.20" - "@storybook/core-common" "6.4.20" - "@storybook/node-logger" "6.4.20" - "@storybook/theming" "6.4.20" - "@storybook/ui" "6.4.20" - "@types/node" "^14.0.10" + "@storybook/addons" "6.5.6" + "@storybook/core-client" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/node-logger" "6.5.6" + "@storybook/theming" "6.5.6" + "@storybook/ui" "6.5.6" + "@types/node" "^14.0.10 || ^16.0.0" babel-loader "^8.0.0" case-sensitive-paths-webpack-plugin "^2.3.0" chalk "^4.1.0" core-js "^3.8.2" css-loader "^5.0.1" express "^4.17.1" - file-system-cache "^1.0.5" find-up "^5.0.0" fs-extra "^9.0.1" html-webpack-plugin "^5.0.0" - node-fetch "^2.6.1" + node-fetch "^2.6.7" process "^0.11.10" read-pkg-up "^7.0.1" regenerator-runtime "^0.13.7" resolve-from "^5.0.0" style-loader "^2.0.0" - telejson "^5.3.2" + telejson "^6.0.8" terser-webpack-plugin "^5.0.3" ts-dedent "^2.0.0" util-deprecate "^1.0.2" @@ -4478,10 +4302,27 @@ webpack-dev-middleware "^4.1.0" webpack-virtual-modules "^0.4.1" -"@storybook/node-logger@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.4.20.tgz#41618be54ba6f98a7c96bdb2c92ebd34c7231505" - integrity sha512-8E34tK4NPkXn+Ga20d5Oba0mVem9w60B2bBQk66TMGXJdZnAqO9xrBlVYEQkeb58g4Mb2WVBFTY6fsDVHwzZyw== +"@storybook/mdx1-csf@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@storybook/mdx1-csf/-/mdx1-csf-0.0.1.tgz#d4184e3f6486fade9f7a6bfaf934d9bc07718d5b" + integrity sha512-4biZIWWzoWlCarMZmTpqcJNgo/RBesYZwGFbQeXiGYsswuvfWARZnW9RE9aUEMZ4XPn7B1N3EKkWcdcWe/K2tg== + dependencies: + "@babel/generator" "^7.12.11" + "@babel/parser" "^7.12.11" + "@babel/preset-env" "^7.12.11" + "@babel/types" "^7.12.11" + "@mdx-js/mdx" "^1.6.22" + "@types/lodash" "^4.14.167" + js-string-escape "^1.0.1" + loader-utils "^2.0.0" + lodash "^4.17.21" + prettier ">=2.2.1 <=2.3.0" + ts-dedent "^2.0.0" + +"@storybook/node-logger@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.5.6.tgz#24422886f894978b10c2e23feb6bf0657f5e514f" + integrity sha512-bT0R0skDWW9iJS8AIG9zfSF8XzTffdc8hRlpXUF9+VQKds+8H9FyoV4tl8ySCuNNunt8Ic9GvW3Fakq49FUcgw== dependencies: "@types/npmlog" "^4.1.2" chalk "^4.1.0" @@ -4489,24 +4330,24 @@ npmlog "^5.0.1" pretty-hrtime "^1.0.3" -"@storybook/postinstall@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.4.20.tgz#df8b410e430f594245b6eabb5fd521b2ca3ec10d" - integrity sha512-BcDNLfW5F265VMntFfLzBnlOf/EYRWwM8puoQgjZGCHCEErJZ89BvWx/lOGY/t3yc5Go0QXp86Ybq30kNFHGwg== +"@storybook/postinstall@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.5.6.tgz#df0bab91b0d1f576a9d4d63842ad56238e26c9d6" + integrity sha512-MPrlmVOXbfAiSaGzG1jgUM4l2zXOI9h3pvrh+0dQLJYP3tjO+5RNmJKMjH8c1PkTcAeoHkp1gUYZ0KKy2imRRQ== dependencies: core-js "^3.8.2" -"@storybook/preview-web@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/preview-web/-/preview-web-6.4.20.tgz#3cf1507c63df376776d96bde7332439f177dcb47" - integrity sha512-rn06XQRLdlwGtmbqTRRq6fEWaNruxA2pQzdOqBSww30u6PMV8IE7RiAHYDbGwJOk5DatliU+16duRNVR4QoHcw== - dependencies: - "@storybook/addons" "6.4.20" - "@storybook/channel-postmessage" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/store" "6.4.20" +"@storybook/preview-web@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/preview-web/-/preview-web-6.5.6.tgz#d4cf4a90a27819aa3315b48b21df6a515bb49b7a" + integrity sha512-fjWfe1ZqtMMS3UFiL6XDYVR0T5QVhXIp/Ax19tuW2VJ/NL/zl4+c9v9lx5jSY5iD5tKwYYRrbH8VS2Pm/CNAzA== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/channel-postmessage" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/store" "6.5.6" ansi-to-html "^0.6.11" core-js "^3.8.2" global "^4.4.0" @@ -4518,48 +4359,59 @@ unfetch "^4.2.0" util-deprecate "^1.0.2" -"@storybook/react-docgen-typescript-plugin@1.0.2-canary.253f8c1.0": - version "1.0.2-canary.253f8c1.0" - resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.253f8c1.0.tgz#f2da40e6aae4aa586c2fb284a4a1744602c3c7fa" - integrity sha512-mmoRG/rNzAiTbh+vGP8d57dfcR2aP+5/Ll03KKFyfy5FqWFm/Gh7u27ikx1I3LmVMI8n6jh5SdWMkMKon7/tDw== +"@storybook/react-docgen-typescript-plugin@1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0": + version "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" + resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0.tgz#3103532ff494fb7dc3cf835f10740ecf6a26c0f9" + integrity sha512-eVg3BxlOm2P+chijHBTByr90IZVUtgRW56qEOLX7xlww2NBuKrcavBlcmn+HH7GIUktquWkMPtvy6e0W0NgA5w== dependencies: debug "^4.1.1" endent "^2.0.1" find-cache-dir "^3.3.1" flat-cache "^3.0.4" micromatch "^4.0.2" - react-docgen-typescript "^2.0.0" + react-docgen-typescript "^2.1.1" tslib "^2.0.0" "@storybook/react@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.4.20.tgz#1f0bd7678fc3f6c97269a5f0f1cdaf0ca82bb09c" - integrity sha512-3AN0CQzYdL8+hasmU7lXv+xHXtbUOQ8dPogUm4ecW7ZnuL7/TKxJ5SBcL4UlDWY8BASI++ZkauCH0ncNkQ83Ew== + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.5.6.tgz#9b7ddc678790a8af100c26b8d47c332bc6f4fc84" + integrity sha512-lU2ggvZe9PbKWK4cbwHI6eDT7tlRAqdx489kLrfKhGTUVE8zhc/uosVEMpxbxS/zf1YYnXumr2DH1Uw2S3JeqA== dependencies: "@babel/preset-flow" "^7.12.1" "@babel/preset-react" "^7.12.10" - "@pmmmwh/react-refresh-webpack-plugin" "^0.5.1" - "@storybook/addons" "6.4.20" - "@storybook/core" "6.4.20" - "@storybook/core-common" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/node-logger" "6.4.20" - "@storybook/react-docgen-typescript-plugin" "1.0.2-canary.253f8c1.0" + "@pmmmwh/react-refresh-webpack-plugin" "^0.5.3" + "@storybook/addons" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core" "6.5.6" + "@storybook/core-common" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/docs-tools" "6.5.6" + "@storybook/node-logger" "6.5.6" + "@storybook/react-docgen-typescript-plugin" "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" "@storybook/semver" "^7.3.2" - "@storybook/store" "6.4.20" + "@storybook/store" "6.5.6" + "@types/estree" "^0.0.51" + "@types/node" "^14.14.20 || ^16.0.0" "@types/webpack-env" "^1.16.0" + acorn "^7.4.1" + acorn-jsx "^5.3.1" + acorn-walk "^7.2.0" babel-plugin-add-react-displayname "^0.0.5" - babel-plugin-named-asset-import "^0.3.1" babel-plugin-react-docgen "^4.2.1" core-js "^3.8.2" + escodegen "^2.0.0" + fs-extra "^9.0.1" global "^4.4.0" + html-tags "^3.1.0" lodash "^4.17.21" prop-types "^15.7.2" + react-element-to-jsx-string "^14.3.4" react-refresh "^0.11.0" read-pkg-up "^7.0.1" regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" - webpack "4" + util-deprecate "^1.0.2" + webpack ">=4.43.0 <6.0.0" "@storybook/router@6.2.9": version "6.2.9" @@ -4577,22 +4429,14 @@ qs "^6.10.0" ts-dedent "^2.0.0" -"@storybook/router@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.4.20.tgz#4344e699332837fe51142b215e315b0a9e6a6e44" - integrity sha512-lwTBtuq9gNywkVs1rye50dPF6pJEGHhZ+2MOTMtASjuM8KIL/wI3OYwRDnDf/98FcinFAeBcEPrEHmV5sAW73w== +"@storybook/router@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.6.tgz#83b442635c5a32a0eb0b4cd3f2da8b7cbecc32e7" + integrity sha512-SL9X/+bkJxafH5V9r65liMgs+EvGddMCQ/4JP/p3GIu6+2G3pW613Ww5sqCBCB1R4zoYcsMT7F1q/D5NWByVHg== dependencies: - "@storybook/client-logger" "6.4.20" + "@storybook/client-logger" "6.5.6" core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - history "5.0.0" - lodash "^4.17.21" - memoizerific "^1.11.3" - qs "^6.10.0" - react-router "^6.0.0" - react-router-dom "^6.0.0" - ts-dedent "^2.0.0" + regenerator-runtime "^0.13.7" "@storybook/semver@^7.3.2": version "7.3.2" @@ -4602,14 +4446,14 @@ core-js "^3.6.5" find-up "^4.1.0" -"@storybook/source-loader@6.4.20", "@storybook/source-loader@^6.4.9": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.4.20.tgz#bb8742ca7b23d4e239be487b2b8843083a24db99" - integrity sha512-mBnfZrwCBxMFdAI5NSs+oxQKLIv4IOM2U3V5n/4NjPvVDmfPt5ozQ/v/1yyVFsuneAXw6xfpS24cI4M9GenUgQ== +"@storybook/source-loader@6.5.6", "@storybook/source-loader@^6.4.9": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.5.6.tgz#e20c6a827c2021bbbab05d0bb2c1e6fb66e133a9" + integrity sha512-KsV9DU3JucYjWHq55f1bOu52mprgSpTIRUnvK2BojQdjY+kqN2ow0pP/I9FZzNpEKKsGFWdnREfYM5dYHZyvLw== dependencies: - "@storybook/addons" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/addons" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" core-js "^3.8.2" estraverse "^5.2.0" global "^4.4.0" @@ -4618,15 +4462,15 @@ prettier ">=2.2.1 <=2.3.0" regenerator-runtime "^0.13.7" -"@storybook/store@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.4.20.tgz#ee02210ec8b8340468ff9ffc1b953acfe485af5f" - integrity sha512-TXrjlBnXgarqZ+Z8Apg8UVkHbKHRkBJmsrlTRucwf8N9mE6EQxRfpqvghcQW3yj2NR1QFdtn13WKF+ZBeHAqgQ== +"@storybook/store@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.5.6.tgz#0250d92f10d9590f4df2f7e6e11a07103055c350" + integrity sha512-ftU40jN5IV26Mj4QjqgsooWFYXDNtcwByH9JrbIlZv7L8HySqVHqeOMrk2Bbt2EkM3urhc5yd0Bbr8EuKlbZVQ== dependencies: - "@storybook/addons" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/csf" "0.0.2--canary.87bc651.0" + "@storybook/addons" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/csf" "0.0.2--canary.4566f4d.1" core-js "^3.8.2" fast-deep-equal "^3.1.3" global "^4.4.0" @@ -4639,6 +4483,24 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/telemetry@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-6.5.6.tgz#a7da1fa0019b307ceac24e65ff1e14ee0f36a8a5" + integrity sha512-l0vbStCgVA9u0ITvowZ1LNxmf32vAAdnPqSmB9DdA3ZO2wCpttW9rPyg1O4OV8c5uq7QJZ7mrKZ04p9SLo8wrw== + dependencies: + "@storybook/client-logger" "6.5.6" + "@storybook/core-common" "6.5.6" + chalk "^4.1.0" + core-js "^3.8.2" + detect-package-manager "^2.0.1" + fetch-retry "^5.0.2" + fs-extra "^9.0.1" + global "^4.4.0" + isomorphic-unfetch "^3.1.0" + nanoid "^3.3.1" + read-pkg-up "^7.0.1" + regenerator-runtime "^0.13.7" + "@storybook/theming@6.2.9": version "6.2.9" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.2.9.tgz#16bf40180861f222c7ed1d80abd5d1e3cb315660" @@ -4657,57 +4519,32 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" -"@storybook/theming@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.20.tgz#b015b976a4c5f7648ec213ebb1fd76f3ec38fe85" - integrity sha512-sVGpRYyJHbdme8ozd9AT70VZ24ug6eypAKT7P+cfzImlYJABjmcfaJ+V4rlavoJF1sGnmauJmGoOf40b1U5JZQ== +"@storybook/theming@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.6.tgz#519e1675a7ff5c780f5a41b05b4b679c37b62fe8" + integrity sha512-JEKl9gdVD2Ef9xSwRtaq6EpjJD5xe7X2OP/4e61ucrp/rSOk7SOpYUZYQh6PhYLGhnGbgQkedVVc9CUhK8bs6Q== dependencies: - "@emotion/core" "^10.1.1" - "@emotion/is-prop-valid" "^0.8.6" - "@emotion/styled" "^10.0.27" - "@storybook/client-logger" "6.4.20" + "@storybook/client-logger" "6.5.6" core-js "^3.8.2" - deep-object-diff "^1.1.0" - emotion-theming "^10.0.27" - global "^4.4.0" - memoizerific "^1.11.3" - polished "^4.0.5" - resolve-from "^5.0.0" - ts-dedent "^2.0.0" + regenerator-runtime "^0.13.7" -"@storybook/ui@6.4.20": - version "6.4.20" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.20.tgz#30e8fba0877b66000841046133d3dc098a807d13" - integrity sha512-QY077l+S79RtdIdBahF2zu1lKqGlBqHeyB3k4W2nCUKJpqmFyzEV6SihkOZyKKe6dX0xDLQvOHIgsSK9+rACfg== - dependencies: - "@emotion/core" "^10.1.1" - "@storybook/addons" "6.4.20" - "@storybook/api" "6.4.20" - "@storybook/channels" "6.4.20" - "@storybook/client-logger" "6.4.20" - "@storybook/components" "6.4.20" - "@storybook/core-events" "6.4.20" - "@storybook/router" "6.4.20" +"@storybook/ui@6.5.6": + version "6.5.6" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.5.6.tgz#3b312728c11d1bdba9cac41d18d91b6806496028" + integrity sha512-EU/YGczcA/v7jHlXIJ7CytK+FH2X9tlsjQbcQW8niPkwtk/gvG7vTfNhanbDlWMKnDLuFWhyHJ33nrW2mIyqkw== + dependencies: + "@storybook/addons" "6.5.6" + "@storybook/api" "6.5.6" + "@storybook/channels" "6.5.6" + "@storybook/client-logger" "6.5.6" + "@storybook/components" "6.5.6" + "@storybook/core-events" "6.5.6" + "@storybook/router" "6.5.6" "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.4.20" - copy-to-clipboard "^3.3.1" + "@storybook/theming" "6.5.6" core-js "^3.8.2" - core-js-pure "^3.8.2" - downshift "^6.0.15" - emotion-theming "^10.0.27" - fuse.js "^3.6.1" - global "^4.4.0" - lodash "^4.17.21" - markdown-to-jsx "^7.1.3" - memoizerific "^1.11.3" - polished "^4.0.5" - qs "^6.10.0" - react-draggable "^4.4.3" - react-helmet-async "^1.0.7" - react-sizeme "^3.0.1" regenerator-runtime "^0.13.7" resolve-from "^5.0.0" - store2 "^2.12.0" "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" @@ -4724,41 +4561,81 @@ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== +"@svgr/babel-plugin-add-jsx-attribute@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18" + integrity sha512-MdPdhdWLtQsjd29Wa4pABdhWbaRMACdM1h31BY+c6FghTZqNGT7pEYdBoaGeKtdTOBC/XNFQaKVj+r/Ei2ryWA== + "@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== +"@svgr/babel-plugin-remove-jsx-attribute@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.0.0.tgz#58654908beebfa069681a83332544b17e5237e89" + integrity sha512-aVdtfx9jlaaxc3unA6l+M9YRnKIZjOhQPthLKqmTXC8UVkBLDRGwPKo+r8n3VZN8B34+yVajzPTZ+ptTSuZZCw== + "@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== +"@svgr/babel-plugin-remove-jsx-empty-expression@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.0.0.tgz#d06dd6e8a8f603f92f9979bb9990a1f85a4f57ba" + integrity sha512-Ccj42ApsePD451AZJJf1QzTD1B/BOU392URJTeXFxSK709i0KUsGtbwyiqsKu7vsYxpTM0IA5clAKDyf9RCZyA== + "@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== +"@svgr/babel-plugin-replace-jsx-attribute-value@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.0.0.tgz#0b85837577b02c31c09c758a12932820f5245cee" + integrity sha512-88V26WGyt1Sfd1emBYmBJRWMmgarrExpKNVmI9vVozha4kqs6FzQJ/Kp5+EYli1apgX44518/0+t9+NU36lThQ== + "@svgr/babel-plugin-svg-dynamic-title@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== +"@svgr/babel-plugin-svg-dynamic-title@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.0.0.tgz#28236ec26f7ab9d486a487d36ae52d58ba15676f" + integrity sha512-F7YXNLfGze+xv0KMQxrl2vkNbI9kzT9oDK55/kUuymh1ACyXkMV+VZWX1zEhSTfEKh7VkHVZGmVtHg8eTZ6PRg== + "@svgr/babel-plugin-svg-em-dimensions@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== +"@svgr/babel-plugin-svg-em-dimensions@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.0.0.tgz#40267c5dea1b43c4f83a0eb6169e08b43d8bafce" + integrity sha512-+rghFXxdIqJNLQK08kwPBD3Z22/0b2tEZ9lKiL/yTfuyj1wW8HUXu4bo/XkogATIYuXSghVQOOCwURXzHGKyZA== + "@svgr/babel-plugin-transform-react-native-svg@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== +"@svgr/babel-plugin-transform-react-native-svg@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.0.0.tgz#eb688d0a5f539e34d268d8a516e81f5d7fede7c9" + integrity sha512-VaphyHZ+xIKv5v0K0HCzyfAaLhPGJXSk2HkpYfXIOKb7DjLBv0soHDxNv6X0vr2titsxE7klb++u7iOf7TSrFQ== + "@svgr/babel-plugin-transform-svg-component@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== +"@svgr/babel-plugin-transform-svg-component@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.2.0.tgz#7ba61d9fc1fb42b0ba1a04e4630019fa7e993c4f" + integrity sha512-bhYIpsORb++wpsp91fymbFkf09Z/YEKR0DnFjxvN+8JHeCUD2unnh18jIMKnDJTWtvpTaGYPXELVe4OOzFI0xg== + "@svgr/babel-preset@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" @@ -4773,6 +4650,20 @@ "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" "@svgr/babel-plugin-transform-svg-component" "^5.5.0" +"@svgr/babel-preset@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.2.0.tgz#1d3ad8c7664253a4be8e4a0f0e6872f30d8af627" + integrity sha512-4WQNY0J71JIaL03DRn0vLiz87JXx0b9dYm2aA8XHlQJQoixMl4r/soYHm8dsaJZ3jWtkCiOYy48dp9izvXhDkQ== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^6.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^6.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^6.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "^6.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "^6.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "^6.0.0" + "@svgr/babel-plugin-transform-svg-component" "^6.2.0" + "@svgr/core@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" @@ -4782,6 +4673,15 @@ camelcase "^6.2.0" cosmiconfig "^7.0.0" +"@svgr/core@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.2.1.tgz#195de807a9f27f9e0e0d678e01084b05c54fdf61" + integrity sha512-NWufjGI2WUyrg46mKuySfviEJ6IxHUOm/8a3Ph38VCWSp+83HBraCQrpEM3F3dB6LBs5x8OElS8h3C0oOJaJAA== + dependencies: + "@svgr/plugin-jsx" "^6.2.1" + camelcase "^6.2.0" + cosmiconfig "^7.0.1" + "@svgr/hast-util-to-babel-ast@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" @@ -4789,6 +4689,14 @@ dependencies: "@babel/types" "^7.12.6" +"@svgr/hast-util-to-babel-ast@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.2.1.tgz#ae065567b74cbe745afae617053adf9a764bea25" + integrity sha512-pt7MMkQFDlWJVy9ULJ1h+hZBDGFfSCwlBNW1HkLnVi7jUhyEXUaGYWi1x6bM2IXuAR9l265khBT4Av4lPmaNLQ== + dependencies: + "@babel/types" "^7.15.6" + entities "^3.0.1" + "@svgr/plugin-jsx@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" @@ -4799,6 +4707,16 @@ "@svgr/hast-util-to-babel-ast" "^5.5.0" svg-parser "^2.0.2" +"@svgr/plugin-jsx@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.2.1.tgz#5668f1d2aa18c2f1bb7a1fc9f682d3f9aed263bd" + integrity sha512-u+MpjTsLaKo6r3pHeeSVsh9hmGRag2L7VzApWIaS8imNguqoUwDq/u6U/NDmYs/KAsrmtBjOEaAAPbwNGXXp1g== + dependencies: + "@babel/core" "^7.15.5" + "@svgr/babel-preset" "^6.2.0" + "@svgr/hast-util-to-babel-ast" "^6.2.1" + svg-parser "^2.0.2" + "@svgr/plugin-svgo@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" @@ -4808,6 +4726,15 @@ deepmerge "^4.2.2" svgo "^1.2.2" +"@svgr/plugin-svgo@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.2.0.tgz#4cbe6a33ccccdcae4e3b63ded64cc1cbe1faf48c" + integrity sha512-oDdMQONKOJEbuKwuy4Np6VdV6qoaLLvoY86hjvQEgU82Vx1MSWRyYms6Sl0f+NtqxLI/rDVufATbP/ev996k3Q== + dependencies: + cosmiconfig "^7.0.1" + deepmerge "^4.2.2" + svgo "^2.5.0" + "@svgr/webpack@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.5.0.tgz#aae858ee579f5fa8ce6c3166ef56c6a1b381b640" @@ -4822,6 +4749,20 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@svgr/webpack@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.2.1.tgz#ef5d51c1b6be4e7537fb9f76b3f2b2e22b63c58d" + integrity sha512-h09ngMNd13hnePwgXa+Y5CgOjzlCvfWLHg+MBnydEedAnuLRzUHUJmGS3o2OsrhxTOOqEsPOFt5v/f6C5Qulcw== + dependencies: + "@babel/core" "^7.15.5" + "@babel/plugin-transform-react-constant-elements" "^7.14.5" + "@babel/preset-env" "^7.15.6" + "@babel/preset-react" "^7.14.5" + "@babel/preset-typescript" "^7.15.0" + "@svgr/core" "^6.2.1" + "@svgr/plugin-jsx" "^6.2.1" + "@svgr/plugin-svgo" "^6.2.0" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -4861,9 +4802,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" - integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + version "7.17.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== dependencies: "@babel/types" "^7.3.0" @@ -4882,13 +4823,6 @@ dependencies: "@types/node" "*" -"@types/buble@^0.20.0": - version "0.20.1" - resolved "https://registry.yarnpkg.com/@types/buble/-/buble-0.20.1.tgz#cba009801fd417b0d2eb8fa6824b537842e05803" - integrity sha512-itmN3lGSTvXg9IImY5j290H+n0B3PpZST6AgEfJJDXfaMx2cdJJZro3/Ay+bZZdIAa25Z5rnoo9rHiPCbANZoQ== - dependencies: - magic-string "^0.25.0" - "@types/child-process-promise@^2.2.1": version "2.2.2" resolved "https://registry.yarnpkg.com/@types/child-process-promise/-/child-process-promise-2.2.2.tgz#ac7d640a6289f7caa6102d92d83a0123a07a4937" @@ -4896,18 +4830,6 @@ dependencies: "@types/node" "*" -"@types/color-convert@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22" - integrity sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ== - dependencies: - "@types/color-name" "*" - -"@types/color-name@*": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - "@types/connect-history-api-fallback@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" @@ -4932,9 +4854,17 @@ "@types/estree" "*" "@types/eslint@*": - version "8.4.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304" - integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== + version "8.4.2" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.2.tgz#48f2ac58ab9c631cb68845c3d956b28f79fad575" + integrity sha512-Z1nseZON+GEnFjJc04sv4NSALGjhFwy6K0HXt7qsn5ArfAKtb63dXNJHf+1YW6IpOIYRBGUbu3GwJdj8DGnCjA== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/eslint@^7.28.2": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" + integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -4965,7 +4895,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*": +"@types/express@*", "@types/express@^4.17.13": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== @@ -4975,11 +4905,6 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/github-slugger@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@types/github-slugger/-/github-slugger-1.3.0.tgz#16ab393b30d8ae2a111ac748a015ac05a1fc5524" - integrity sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g== - "@types/glob@*", "@types/glob@^7.1.1": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -5002,6 +4927,11 @@ dependencies: "@types/unist" "*" +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + "@types/html-minifier-terser@^5.0.0": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" @@ -5013,9 +4943,9 @@ integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== "@types/http-proxy@^1.17.8": - version "1.17.8" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" - integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA== + version "1.17.9" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" + integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== dependencies: "@types/node" "*" @@ -5051,7 +4981,15 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/jest@^27.5.0": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.1.tgz#2c8b6dc6ff85c33bcd07d0b62cb3d19ddfdb3ab9" + integrity sha512-fUy7YRpT+rHXto1YlL+J9rs0uLGyiqVt3ZOTQR+4ROc47yNl8WLdVLgUloBRhOxP1PZvguHl44T3H0wAWxahYQ== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -5059,12 +4997,12 @@ "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/lodash@^4.14.53": - version "4.14.181" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d" - integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag== +"@types/lodash@^4.14.167": + version "4.14.182" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" + integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== "@types/mdast@^3.0.0": version "3.0.10" @@ -5097,34 +5035,24 @@ form-data "^3.0.0" "@types/node@*", "@types/node@>= 8", "@types/node@^17.0.5": - version "17.0.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" - integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== - -"@types/node@12.12.50": - version "12.12.50" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" - integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== - -"@types/node@16.9.1": - version "16.9.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708" - integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== - -"@types/node@^10.11.7": - version "10.17.60" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" - integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== + version "17.0.38" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" + integrity sha512-5jY9RhV7c0Z4Jy09G+NIDTsCZ5G0L5n+Z+p+Y7t5VJHM30bgwzSjVtlcBxqAj+6L/swIlvtOSzr8rBk/aNyV2g== "@types/node@^11.9.4": version "11.15.54" resolved "https://registry.yarnpkg.com/@types/node/-/node-11.15.54.tgz#59ed60e7b0d56905a654292e8d73275034eb6283" integrity sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g== -"@types/node@^14.0.10": - version "14.18.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.12.tgz#0d4557fd3b94497d793efd4e7d92df2f83b4ef24" - integrity sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A== +"@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": + version "16.11.38" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.38.tgz#be0edd097b23eace6c471c525a74b3f98803017f" + integrity sha512-hjO/0K140An3GWDw2HJfq7gko3wWeznbjXgg+rzPdVzhe198hp4x2i1dgveAOEiFKd8sOilAxzoSJiVv5P/CUg== + +"@types/node@^14.14.31": + version "14.18.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.20.tgz#268f028b36eaf51181c3300252f605488c4f0650" + integrity sha512-Q8KKwm9YqEmUBRsqJ2GWJDtXltBDxTdC4m5vTdXBolu2PeQh8LX+f6BTwU+OuXPu37fLxoN6gidqBmnky36FXA== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -5136,11 +5064,6 @@ resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.4.tgz#30eb872153c7ead3e8688c476054ddca004115f6" integrity sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ== -"@types/overlayscrollbars@^1.12.0": - version "1.12.1" - resolved "https://registry.yarnpkg.com/@types/overlayscrollbars/-/overlayscrollbars-1.12.1.tgz#fb637071b545834fb12aea94ee309a2ff4cdc0a8" - integrity sha512-V25YHbSoKQN35UasHf0EKD9U2vcmexRSp78qa8UglxFH8H3D+adEa9zGZwrqpH4TdvqeMrgMqVqsLB4woAryrQ== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -5157,9 +5080,9 @@ integrity sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ== "@types/prop-types@*": - version "15.7.4" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" - integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== "@types/q@^1.5.1": version "1.5.5" @@ -5183,6 +5106,32 @@ dependencies: "@types/react" "*" +"@types/react-router-config@*": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" + integrity sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router-dom@*": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.18" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" + integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-syntax-highlighter@11.0.5": version "11.0.5" resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087" @@ -5191,18 +5140,18 @@ "@types/react" "*" "@types/react@*": - version "17.0.43" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" - integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== + version "18.0.10" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.10.tgz#5692944d4a45e204fb7a981eb1388afe919cf4d0" + integrity sha512-dIugadZuIPrRzvIEevIu7A1smqOAjkSMv8qOfwPt9Ve6i6JT/FQcCHyk2qIAxwsQNKZt5/oGR0T4z9h2dXRAkg== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" csstype "^3.0.2" "@types/react@^16.0.0": - version "16.14.24" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.24.tgz#f2c5e9fa78f83f769884b83defcf7924b9eb5c82" - integrity sha512-e7U2WC8XQP/xfR7bwhOhNFZKPTfW1ph+MiqtudKb8tSV8RyCsovQx2sNVtKoOryjxFKpHPPC/yNiGfdeVM5Gyw== + version "16.14.26" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.26.tgz#82540a240ba7207ebe87d9579051bc19c9ef7605" + integrity sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -5220,10 +5169,10 @@ dependencies: "@types/node" "*" -"@types/retry@^0.12.0": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" - integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== "@types/sax@^1.2.1": version "1.2.4" @@ -5252,10 +5201,10 @@ "@types/mime" "^1" "@types/node" "*" -"@types/sinonjs__fake-timers@^6.0.1": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz#0ecc1b9259b76598ef01942f547904ce61a6a77d" - integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -5290,9 +5239,9 @@ integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== "@types/uglify-js@*": - version "3.13.1" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea" - integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ== + version "3.13.2" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.2.tgz#1044c1713fb81cb1ceef29ad8a9ee1ce08d690ef" + integrity sha512-/xFrPIo+4zOeNGtVMbf9rUm0N+i4pDf1ynExomqtokIJmVzR3962lJ1UE+MmexMkA0cmN9oTzg5Xcbwge0Ij2Q== dependencies: source-map "^0.6.1" @@ -5302,9 +5251,9 @@ integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== "@types/webpack-env@^1.16.0": - version "1.16.3" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a" - integrity sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw== + version "1.17.0" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0" + integrity sha512-eHSaNYEyxRA5IAG0Ym/yCyf86niZUIF/TpWKofQI/CVfh5HsMEUyfE2kwFxha4ow0s5g0LfISQxpDKjbRDrizw== "@types/webpack-sources@*": version "3.2.0" @@ -5327,7 +5276,7 @@ anymatch "^3.0.0" source-map "^0.6.0" -"@types/ws@^8.2.2": +"@types/ws@^8.2.2", "@types/ws@^8.5.1": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== @@ -5354,12 +5303,82 @@ "@types/yargs-parser" "*" "@types/yauzl@^2.9.1": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" - integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== + version "2.10.0" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" + integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== dependencies: "@types/node" "*" +"@typescript-eslint/eslint-plugin@^4.19.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" + integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== + dependencies: + "@typescript-eslint/experimental-utils" "4.33.0" + "@typescript-eslint/scope-manager" "4.33.0" + debug "^4.3.1" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" + integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.33.0" + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/typescript-estree" "4.33.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@^4.19.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" + integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== + dependencies: + "@typescript-eslint/scope-manager" "4.33.0" + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/typescript-estree" "4.33.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" + integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + +"@typescript-eslint/types@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" + integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== + +"@typescript-eslint/typescript-estree@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" + integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" + integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== + dependencies: + "@typescript-eslint/types" "4.33.0" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -5681,9 +5700,9 @@ JSONStream@^1.0.4, JSONStream@^1.3.4: through ">=2.2.7 <3" abab@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== abbrev@1: version "1.1.1" @@ -5726,7 +5745,7 @@ acorn-jsx@^5.0.1, acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-node@^1.6.1: +acorn-node@^1.8.2: version "1.8.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== @@ -5766,14 +5785,14 @@ acorn@^7.0.0, acorn@^7.4.0, acorn@^7.4.1: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== -address@1.1.2, address@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" - integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== +address@^1.0.1, address@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/address/-/address-1.2.0.tgz#d352a62c92fee90f89a693eccd2a8b2139ab02d9" + integrity sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig== agent-base@4, agent-base@^4.3.0: version "4.3.0" @@ -5782,11 +5801,6 @@ agent-base@4, agent-base@^4.3.0: dependencies: es6-promisify "^5.0.0" -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - agent-base@~4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" @@ -5881,7 +5895,7 @@ ajv@6.5.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@~6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -5901,46 +5915,46 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.6.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" -algoliasearch-helper@^3.3.4: - version "3.8.1" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.8.1.tgz#65e1acb7e301309b3c71e587b28a5a6e6619f3fd" - integrity sha512-IGK67xeut0wYRXQw+MlSDYmYK/6e+/a++HVf9MgSWYtPd6QIHWiOKpgMYRJMNF/zMjx0FPA16D/AypgWxSVBnQ== +algoliasearch-helper@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.8.2.tgz#35726dc6d211f49dbab0bf6d37b4658165539523" + integrity sha512-AXxiF0zT9oYwl8ZBgU/eRXvfYhz7cBA5YrLPlw9inZHdaYF0QEya/f1Zp1mPYMXc1v6VkHwBq4pk6/vayBLICg== dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.0.0, algoliasearch@^4.8.4: - version "4.13.0" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.13.0.tgz#e36611fda82b1fc548c156ae7929a7f486e4b663" - integrity sha512-oHv4faI1Vl2s+YC0YquwkK/TsaJs79g2JFg5FDm2rKN12VItPTAeQ7hyJMHarOPPYuCnNC5kixbtcqvb21wchw== - dependencies: - "@algolia/cache-browser-local-storage" "4.13.0" - "@algolia/cache-common" "4.13.0" - "@algolia/cache-in-memory" "4.13.0" - "@algolia/client-account" "4.13.0" - "@algolia/client-analytics" "4.13.0" - "@algolia/client-common" "4.13.0" - "@algolia/client-personalization" "4.13.0" - "@algolia/client-search" "4.13.0" - "@algolia/logger-common" "4.13.0" - "@algolia/logger-console" "4.13.0" - "@algolia/requester-browser-xhr" "4.13.0" - "@algolia/requester-common" "4.13.0" - "@algolia/requester-node-http" "4.13.0" - "@algolia/transporter" "4.13.0" +algoliasearch@^4.0.0, algoliasearch@^4.13.0: + version "4.13.1" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.13.1.tgz#54195c41c9e4bd13ed64982248cf49d4576974fe" + integrity sha512-dtHUSE0caWTCE7liE1xaL+19AFf6kWEcyn76uhcitWpntqvicFHXKFoZe5JJcv9whQOTRM6+B8qJz6sFj+rDJA== + dependencies: + "@algolia/cache-browser-local-storage" "4.13.1" + "@algolia/cache-common" "4.13.1" + "@algolia/cache-in-memory" "4.13.1" + "@algolia/client-account" "4.13.1" + "@algolia/client-analytics" "4.13.1" + "@algolia/client-common" "4.13.1" + "@algolia/client-personalization" "4.13.1" + "@algolia/client-search" "4.13.1" + "@algolia/logger-common" "4.13.1" + "@algolia/logger-console" "4.13.1" + "@algolia/requester-browser-xhr" "4.13.1" + "@algolia/requester-common" "4.13.1" + "@algolia/requester-node-http" "4.13.1" + "@algolia/transporter" "4.13.1" alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ== ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= + integrity sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA== dependencies: string-width "^2.0.0" -ansi-align@^3.0.0: +ansi-align@^3.0.0, ansi-align@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== @@ -5953,11 +5967,11 @@ ansi-colors@^3.0.0: integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== -ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: +ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== @@ -5977,7 +5991,7 @@ ansi-html-community@0.0.8, ansi-html-community@^0.0.8: ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^3.0.0: version "3.0.1" @@ -5989,7 +6003,7 @@ ansi-regex@^4.0.0, ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== -ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -6002,7 +6016,7 @@ ansi-regex@^6.0.1: ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + integrity sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" @@ -6011,13 +6025,23 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.2.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + ansi-to-html@^0.6.11: version "0.6.15" resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.15.tgz#ac6ad4798a00f6aa045535d7f6a9cb9294eebea7" @@ -6025,16 +6049,6 @@ ansi-to-html@^0.6.11: dependencies: entities "^2.0.0" -ansicolors@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= - -any-base@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" - integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== - any-observable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" @@ -6043,7 +6057,7 @@ any-observable@^0.3.0: any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== anymatch@^2.0.0: version "2.0.0" @@ -6064,7 +6078,7 @@ anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: app-root-dir@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" - integrity sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg= + integrity sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g== aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" @@ -6076,7 +6090,7 @@ aproba@^1.0.3, aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -arch@^2.1.0, arch@^2.1.2: +arch@^2.1.0, arch@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== @@ -6130,7 +6144,7 @@ aria-query@^4.2.2: arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== arr-flatten@^1.1.0: version "1.1.0" @@ -6140,7 +6154,7 @@ arr-flatten@^1.1.0: arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== array-differ@^2.0.3: version "2.1.0" @@ -6150,19 +6164,19 @@ array-differ@^2.0.3: array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" - integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + integrity sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA== array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-flatten@^2.1.0: +array-flatten@^2.1.0, array-flatten@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== @@ -6170,23 +6184,23 @@ array-flatten@^2.1.0: array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" - integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= + integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== -array-includes@^3.0.3, array-includes@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" - integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== +array-includes@^3.0.3, array-includes@^3.1.4, array-includes@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.19.5" get-intrinsic "^1.1.1" is-string "^1.0.7" array-union@^1.0.1, array-union@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng== dependencies: array-uniq "^1.0.1" @@ -6195,42 +6209,50 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-union@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" + integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== array.prototype.find@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.2.tgz#6abbd0c2573925d8094f7d23112306af8c16d534" - integrity sha512-00S1O4ewO95OmmJW7EesWfQlrCrLEL8kZ40w3+GkLX2yTt0m2ggcePPa2uHPJ9KUmJvwRq+lCV9bD8Yim23x/Q== + version "2.2.0" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.2.0.tgz#153b8a28ad8965cd86d3117b07e6596af6f2880d" + integrity sha512-sn40qmUiLYAcRb/1HsIQjTTZ1kCy8II8VtZJpMn2Aoen9twULhbWXisfh3HimGqMlHGUul0/TfKCnXg42LuPpQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.19.0" + es-abstract "^1.19.4" + es-shim-unscopables "^1.0.0" array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" - integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.19.0" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446" - integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA== +array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" + integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg== dependencies: - call-bind "^1.0.0" + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.19.0" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" array.prototype.map@^1.0.4: version "1.0.4" @@ -6243,10 +6265,21 @@ array.prototype.map@^1.0.4: es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" +array.prototype.reduce@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" + integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== arrify@^2.0.1: version "2.0.1" @@ -6256,7 +6289,7 @@ arrify@^2.0.1: asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== asn1.js@^5.2.0: version "5.4.1" @@ -6278,7 +6311,7 @@ asn1@~0.2.3: assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== assert@^1.1.1: version "1.5.0" @@ -6291,12 +6324,12 @@ assert@^1.1.1: assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== ast-types@^0.14.2: version "0.14.2" @@ -6325,15 +6358,10 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@0.9.x: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= - async@^2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== dependencies: lodash "^4.17.14" @@ -6345,7 +6373,7 @@ async@^3.2.0, async@^3.2.3: asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== at-least-node@^1.0.0: version "1.0.0" @@ -6355,7 +6383,7 @@ at-least-node@^1.0.0: atob-lite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" - integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= + integrity sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw== atob@^2.1.2: version "2.1.2" @@ -6367,7 +6395,7 @@ attr-accept@^2.0.0: resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== -autoprefixer@10.4.4, autoprefixer@^10.2.5, autoprefixer@^10.3.7, autoprefixer@^10.4.4: +autoprefixer@10.4.4: version "10.4.4" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== @@ -6392,6 +6420,18 @@ autoprefixer@9.7.4: postcss "^7.0.26" postcss-value-parser "^4.0.2" +autoprefixer@^10.3.7, autoprefixer@^10.4.5, autoprefixer@^10.4.7: + version "10.4.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf" + integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA== + dependencies: + browserslist "^4.20.3" + caniuse-lite "^1.0.30001335" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + autoprefixer@^9.8.6: version "9.8.8" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" @@ -6413,7 +6453,7 @@ available-typed-arrays@^1.0.5: aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== aws4@^1.8.0: version "1.11.0" @@ -6421,17 +6461,24 @@ aws4@^1.8.0: integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== axe-core@^4.3.5: - version "4.4.1" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" - integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== + version "4.4.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.2.tgz#dcf7fb6dea866166c3eab33d68208afe4d5f670c" + integrity sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA== -axios@^0.21.0, axios@^0.21.1: +axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" +axios@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== + dependencies: + follow-redirects "^1.14.7" + axios@^0.26.1: version "0.26.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" @@ -6469,10 +6516,10 @@ babel-jest@^24.9.0: chalk "^2.4.2" slash "^2.0.0" -babel-loader@^8.0.0, babel-loader@^8.2.2, babel-loader@^8.2.4: - version "8.2.4" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.4.tgz#95f5023c791b2e9e2ca6f67b0984f39c82ff384b" - integrity sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A== +babel-loader@^8.0.0, babel-loader@^8.2.2, babel-loader@^8.2.4, babel-loader@^8.2.5: + version "8.2.5" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e" + integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ== dependencies: find-cache-dir "^3.3.1" loader-utils "^2.0.0" @@ -6482,7 +6529,7 @@ babel-loader@^8.0.0, babel-loader@^8.2.2, babel-loader@^8.2.4: babel-plugin-add-react-displayname@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz#339d4cddb7b65fd62d1df9db9fe04de134122bd5" - integrity sha1-M51M3be2X9YtHfnbn+BN4TQSK9U= + integrity sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw== babel-plugin-apply-mdx-type-prop@1.6.22: version "1.6.22" @@ -6568,7 +6615,7 @@ babel-plugin-jest-hoist@^24.9.0: dependencies: "@types/babel__traverse" "^7.0.6" -babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8.0: +babel-plugin-macros@^2.0.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== @@ -6597,10 +6644,10 @@ babel-plugin-module-resolver@^3.2.0: reselect "^3.0.1" resolve "^1.4.0" -babel-plugin-named-asset-import@^0.3.1: - version "0.3.8" - resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz#6b7fa43c59229685368683c28bc9734f24524cc2" - integrity sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q== +babel-plugin-named-exports-order@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-named-exports-order/-/babel-plugin-named-exports-order-0.0.2.tgz#ae14909521cf9606094a2048239d69847540cb09" + integrity sha512-OgOYHOLoRK+/mvXU9imKHlG6GkPLYrUCvFXG/CM93R/aNNO8pOOF4aS+S8CCHMDQoNSeiOYEZb/G6RwL95Jktw== babel-plugin-polyfill-corejs2@^0.3.0: version "0.3.1" @@ -6646,7 +6693,7 @@ babel-plugin-react-docgen@^4.2.1: babel-plugin-syntax-jsx@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + integrity sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw== babel-preset-jest@^24.9.0: version "24.9.0" @@ -6669,7 +6716,7 @@ balanced-match@^1.0.0: base16@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" - integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA= + integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== base64-js@^1.0.2, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" @@ -6689,20 +6736,15 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -batch-processor@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" - integrity sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg= - batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== dependencies: tweetnacl "^0.14.3" @@ -6718,6 +6760,11 @@ better-opn@^2.1.1: dependencies: open "^7.0.3" +big-integer@^1.6.7: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -6749,53 +6796,27 @@ bl@^4.0.3, bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" -blob-util@2.0.2: +blob-util@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird-retry@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/bluebird-retry/-/bluebird-retry-0.11.0.tgz#1289ab22cbbc3a02587baad35595351dd0c1c047" - integrity sha1-EomrIsu8OgJYe6rTVZU1HdDBwEc= - -bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.1, bluebird@^3.7.2: +bluebird@3.7.2, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.1, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bmp-js@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" - integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM= - bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== bn.js@^5.0.0, bn.js@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== - -body-parser@1.19.2: - version "1.19.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" - integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.9.7" - raw-body "2.4.3" - type-is "~1.6.18" + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@^1.18.3: +body-parser@1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== @@ -6813,10 +6834,20 @@ body-parser@^1.18.3: type-is "~1.6.18" unpipe "1.0.0" +bonjour-service@^1.0.11: + version "1.0.12" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.12.tgz#28fbd4683f5f2e36feedb833e24ba661cac960c3" + integrity sha512-pMmguXYCu63Ug37DluMKEHdxc+aaIf/ay4YbF8Gxtba+9d3u+rmEWy61VK3Z3hp8Rskok3BunHYnG0dUHAsblw== + dependencies: + array-flatten "^2.1.2" + dns-equal "^1.0.0" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.4" + bonjour@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + integrity sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg== dependencies: array-flatten "^2.1.0" deep-equal "^1.0.1" @@ -6828,7 +6859,7 @@ bonjour@^3.5.0: boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== boxen@1.3.0: version "1.3.0" @@ -6843,7 +6874,7 @@ boxen@1.3.0: term-size "^1.2.0" widest-line "^2.0.0" -boxen@^5.0.0, boxen@^5.0.1, boxen@^5.1.2: +boxen@^5.0.0, boxen@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== @@ -6857,6 +6888,27 @@ boxen@^5.0.0, boxen@^5.0.1, boxen@^5.1.2: widest-line "^3.1.0" wrap-ansi "^7.0.0" +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + +bplist-parser@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.1.1.tgz#d60d5dcc20cba6dc7e1f299b35d3e1f95dafbae6" + integrity sha512-2AEM0FXy8ZxVLBuqX0hqt1gDwcnz2zygEkQ6zaD5Wko/sB9paUNwlpawrFtKeHUAQUOzjVy9AO4oeonqIHKA9Q== + dependencies: + big-integer "^1.6.7" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -6865,6 +6917,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -6896,7 +6955,12 @@ brcast@^2.0.2: brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browser-assert@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/browser-assert/-/browser-assert-1.2.1.tgz#9aaa5a2a8c74685c2ae05bfe46efd606f068c200" + integrity sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ== browser-detect@^0.2.28: version "0.2.28" @@ -6978,25 +7042,15 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@4.14.2: - version "4.14.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.2.tgz#1b3cec458a1ba87588cc5e9be62f19b6d48813ce" - integrity sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw== - dependencies: - caniuse-lite "^1.0.30001125" - electron-to-chromium "^1.3.564" - escalade "^3.0.2" - node-releases "^1.1.61" - -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.19.1, browserslist@^4.20.2, browserslist@^4.8.3: - version "4.20.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" - integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.18.1, browserslist@^4.20.2, browserslist@^4.20.3, browserslist@^4.8.3: + version "4.20.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" + integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== dependencies: - caniuse-lite "^1.0.30001317" - electron-to-chromium "^1.4.84" + caniuse-lite "^1.0.30001332" + electron-to-chromium "^1.4.118" escalade "^3.1.1" - node-releases "^2.0.2" + node-releases "^2.0.3" picocolors "^1.0.0" bser@2.1.1: @@ -7009,7 +7063,7 @@ bser@2.1.1: btoa-lite@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" - integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= + integrity sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA== buble@0.19.6: version "0.19.6" @@ -7026,12 +7080,7 @@ buble@0.19.6: buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer-equal@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" - integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs= + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== buffer-from@^1.0.0: version "1.1.2" @@ -7046,7 +7095,7 @@ buffer-indexof@^1.0.0: buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== buffer@^4.3.0: version "4.9.2" @@ -7057,7 +7106,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.2.0, buffer@^5.2.1, buffer@^5.5.0: +buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -7066,24 +7115,24 @@ buffer@^5.2.0, buffer@^5.2.1, buffer@^5.5.0: ieee754 "^1.1.13" builtin-modules@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" - integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== builtins@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= + integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ== byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" - integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= + integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== byte-size@^5.0.1: version "5.0.1" @@ -7093,7 +7142,7 @@ byte-size@^5.0.1: bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== bytes@3.1.2, bytes@^3.0.0: version "3.1.2" @@ -7101,22 +7150,22 @@ bytes@3.1.2, bytes@^3.0.0: integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== c8@^7.6.0: - version "7.11.0" - resolved "https://registry.yarnpkg.com/c8/-/c8-7.11.0.tgz#b3ab4e9e03295a102c47ce11d4ef6d735d9a9ac9" - integrity sha512-XqPyj1uvlHMr+Y1IeRndC2X5P7iJzJlEJwBpCdBbq2JocXOgJfr+JVfJkyNMGROke5LfKrhSFXGFXnwnRJAUJw== + version "7.11.3" + resolved "https://registry.yarnpkg.com/c8/-/c8-7.11.3.tgz#88c8459c1952ed4f701b619493c9ae732b057163" + integrity sha512-6YBmsaNmqRm9OS3ZbIiL2EZgi1+Xc4O24jL3vMYGE6idixYuGdy76rIfIdltSKDj9DpLNrcXSonUTR1miBD0wA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@istanbuljs/schema" "^0.1.2" + "@istanbuljs/schema" "^0.1.3" find-up "^5.0.0" foreground-child "^2.0.0" - istanbul-lib-coverage "^3.0.1" + istanbul-lib-coverage "^3.2.0" istanbul-lib-report "^3.0.0" - istanbul-reports "^3.0.2" - rimraf "^3.0.0" + istanbul-reports "^3.1.4" + rimraf "^3.0.2" test-exclude "^6.0.0" - v8-to-istanbul "^8.0.0" + v8-to-istanbul "^9.0.0" yargs "^16.2.0" - yargs-parser "^20.2.7" + yargs-parser "^20.2.9" cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3: version "12.0.4" @@ -7207,26 +7256,26 @@ call-bind@^1.0.0, call-bind@^1.0.2: call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + integrity sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw== caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + integrity sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ== dependencies: callsites "^2.0.0" caller-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + integrity sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A== dependencies: caller-callsite "^2.0.0" callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + integrity sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ== callsites@^3.0.0: version "3.1.0" @@ -7249,7 +7298,7 @@ camelcase-css@2.0.1, camelcase-css@^2.0.1: camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + integrity sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ== dependencies: camelcase "^2.0.0" map-obj "^1.0.0" @@ -7257,7 +7306,7 @@ camelcase-keys@^2.0.0: camelcase-keys@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" - integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= + integrity sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q== dependencies: camelcase "^4.1.0" map-obj "^2.0.0" @@ -7275,12 +7324,12 @@ camelcase-keys@^6.2.2: camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + integrity sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw== camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= + integrity sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw== camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" @@ -7302,10 +7351,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001317: - version "1.0.30001325" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606" - integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335: + version "1.0.30001344" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001344.tgz#8a1e7fdc4db9c2ec79a05e9fd68eb93a761888bb" + integrity sha512-0ZFjnlCaXNOAYcV7i+TtdKBp0L/3XEU2MF/x6Du1lrh+SRX4IfzIVL4HNJg5pB2PmFb8rszIGyOvsZnqqRoc2g== capture-exit@^2.0.0: version "2.0.0" @@ -7314,14 +7363,6 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" -cardinal@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" - integrity sha1-fMEFXYItISlU0HsIXeolHMe8VQU= - dependencies: - ansicolors "~0.3.2" - redeyed "~2.1.0" - case-sensitive-paths-webpack-plugin@^2.3.0: version "2.4.0" resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" @@ -7330,7 +7371,7 @@ case-sensitive-paths-webpack-plugin@^2.3.0: caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== ccount@^1.0.0, ccount@^1.0.3: version "1.1.0" @@ -7346,19 +7387,10 @@ chalk@2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + integrity sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -7366,13 +7398,14 @@ chalk@^1.0.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" @@ -7415,12 +7448,24 @@ chardet@^0.7.0: check-more-types@2.24.0, check-more-types@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" - integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= + integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" cheerio@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" - integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4= + integrity sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA== dependencies: css-select "~1.2.0" dom-serializer "~0.1.0" @@ -7439,10 +7484,24 @@ cheerio@^0.22.0: lodash.reject "^4.4.0" lodash.some "^4.4.0" +cheerio@^1.0.0-rc.10: + version "1.0.0-rc.11" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.11.tgz#1be84be1a126958366bcc57a11648cd9b30a60c2" + integrity sha512-bQwNaDIBKID5ts/DsdhxrjqFXYfLw4ste+wMKqWA8DyKcS4qwsPP4Bk8ZNaTJjvpiX/qW3BT4sU7d6Bh5i+dag== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + tslib "^2.4.0" + child-process-promise@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/child-process-promise/-/child-process-promise-2.2.1.tgz#4730a11ef610fad450b8f223c79d31d7bdad8074" - integrity sha1-RzChHvYQ+tRQuPIjx50x172tgHQ= + integrity sha512-Fi4aNdqBsr0mv+jgWxcZ/7rAIC2mgihrptyVI4foh/rrjY/3BNjfP9+oaiFx/fzim+1ZyCNBae0DlyfQhSugog== dependencies: cross-spawn "^4.0.2" node-version "^1.0.0" @@ -7467,7 +7526,7 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.1, chokidar@^3.5.2: +chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -7502,10 +7561,10 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^3.1.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" - integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== +ci-info@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.1.tgz#58331f6f472a25fe3a50a351ae3052936c2c7f32" + integrity sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -7549,7 +7608,7 @@ clean-css@^4.2.3: dependencies: source-map "~0.6.0" -clean-css@^5.1.2, clean-css@^5.2.2: +clean-css@^5.2.2, clean-css@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.0.tgz#ad3d8238d5f3549e83d5f87205189494bc7cbb59" integrity sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ== @@ -7561,13 +7620,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -clean-stack@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.1.tgz#155bf0b2221bf5f4fba89528d24c5953f17fe3a8" - integrity sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg== - dependencies: - escape-string-regexp "4.0.0" - clean-webpack-plugin@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz#a99d8ec34c1c628a4541567aa7b457446460c62b" @@ -7579,24 +7631,22 @@ clean-webpack-plugin@^3.0.0: cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= + integrity sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg== cli-boxes@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== -cli-cursor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= - dependencies: - restore-cursor "^1.0.1" +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== cli-cursor@^2.0.0, cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== dependencies: restore-cursor "^2.0.0" @@ -7607,66 +7657,35 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-progress@^3.4.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.10.0.tgz#63fd9d6343c598c93542fdfa3563a8b59887d78a" - integrity sha512-kLORQrhYCAtUPLZxqsAt2YJGOvRdt34+O6jl5cQGb7iF3dM55FQZlTR+rQyIK9JUcO9bBMwZsTlND+3dmFU2Cw== - dependencies: - string-width "^4.2.0" - cli-spinners@^2.5.0: version "2.6.1" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== -cli-table3@^0.6.1, cli-table3@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" - integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== +cli-table3@^0.6.1, cli-table3@^0.6.2, cli-table3@~0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.2.tgz#aaf5df9d8b5bf12634dc8b3040806a0c07120d2a" + integrity sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw== dependencies: string-width "^4.2.0" optionalDependencies: - colors "1.4.0" + "@colors/colors" "1.5.0" cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" - integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ= + integrity sha512-f4r4yJnbT++qUPI9NR4XLDLq41gQ+uqnPItWG0F5ZkehuNiTTa3EY0S4AqTSUOeJ7/zU41oWPQSNkW5BqPL9bg== dependencies: slice-ansi "0.0.4" string-width "^1.0.1" -cli-ux@5.6.6: - version "5.6.6" - resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.6.6.tgz#1424f5a9fbddcd796ad985b867a3de7f5a452090" - integrity sha512-4wUB34zoFklcZV0z5YiOM5IqVMMt9c3TK3QYRK3dqyk3XoRC0ybiWDWHfsMDjkKrzsVTw95rXn9NrzSHbae4pg== +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== dependencies: - "@oclif/command" "^1.8.9" - "@oclif/errors" "^1.3.5" - "@oclif/linewrap" "^1.0.0" - "@oclif/screen" "^1.0.4" - ansi-escapes "^4.3.0" - ansi-styles "^4.2.0" - cardinal "^2.1.1" - chalk "^4.1.0" - clean-stack "^3.0.0" - cli-progress "^3.4.0" - extract-stack "^2.0.0" - fs-extra "^8.1" - hyperlinker "^1.0.0" - indent-string "^4.0.0" - is-wsl "^2.2.0" - js-yaml "^3.13.1" - lodash "^4.17.21" - natural-orderby "^2.0.1" - object-treeify "^1.1.4" - password-prompt "^1.1.2" - semver "^7.3.2" + slice-ansi "^3.0.0" string-width "^4.2.0" - strip-ansi "^6.0.0" - supports-color "^8.1.0" - supports-hyperlinks "^2.1.0" - tslib "^2.0.0" cli-width@^2.0.0: version "2.2.1" @@ -7716,14 +7735,14 @@ clone-deep@^4.0.1: clone-response@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + integrity sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q== dependencies: mimic-response "^1.0.0" clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== clsx@^1.1.1: version "1.1.1" @@ -7733,7 +7752,7 @@ clsx@^1.1.1: co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== coa@^2.0.2: version "2.0.2" @@ -7747,14 +7766,14 @@ coa@^2.0.2: coa@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" - integrity sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0= + integrity sha512-KAGck/eNAmCL0dcT3BiuYwLbExK6lduR8DxM3C1TyDzaXhZHyZ8ooX5I5+na2e3dPFuibfxrGdorr0/Lr7RYCQ== dependencies: q "^1.1.2" code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== collapse-white-space@^1.0.2: version "1.0.6" @@ -7764,7 +7783,7 @@ collapse-white-space@^1.0.2: collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== dependencies: map-visit "^1.0.0" object-visit "^1.0.0" @@ -7786,17 +7805,17 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa" - integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ== +color-string@^1.6.0, color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -7814,6 +7833,14 @@ color@^3.0.0, color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + colord@^2.9.1: version "2.9.2" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" @@ -7824,28 +7851,15 @@ colorette@^1.2.2: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== -colorette@^2.0.10, colorette@^2.0.14: +colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16: version "2.0.16" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== -colors@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" - integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" + integrity sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w== columnify@^1.5.4: version "1.6.0" @@ -7872,6 +7886,11 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== +commander@9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" + integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== + commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -7915,7 +7934,7 @@ common-tags@^1.8.0: commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== compare-func@^2.0.0: version "2.0.0" @@ -7933,12 +7952,12 @@ component-emitter@^1.2.1: component-props@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/component-props/-/component-props-1.1.1.tgz#f9b7df9b9927b6e6d97c9bd272aa867670f34944" - integrity sha1-+bffm5kntubZfJvScqqGdnDzSUQ= + integrity sha512-69pIRJs9fCCHRqCz3390YF2LV1Lu6iEMZ5zuVqqUn+G20V9BNXlMs0cWawWeW9g4Ynmg29JmkG6R7/lUJoGd1Q== component-xor@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/component-xor/-/component-xor-0.0.4.tgz#c55d83ccc1b94cd5089a4e93fa7891c7263e59aa" - integrity sha1-xV2DzMG5TNUImk6T+niRxyY+Wao= + integrity sha512-ZIt6sla8gfo+AFVRZoZOertcnD5LJaY2T9CKE2j13NJxQt/mUafD69Bl7/Y4AnpI2LGjiXH7cOfJDx/n2G9edA== compressible@~2.0.14, compressible@~2.0.16: version "2.0.18" @@ -7973,17 +7992,12 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" -compute-scroll-into-view@^1.0.17: - version "1.0.17" - resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" - integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -concat-stream@^1.5.0, concat-stream@^1.6.2: +concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -8011,6 +8025,14 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" +config-point@^0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/config-point/-/config-point-0.4.8.tgz#51864ef9e9e3d7ab2b42c410dd15b2fcb58fd905" + integrity sha512-h2nUOYFQE1CLJeb44TZN0msSH4419I5nvZRiKedsGAq8NZc0uGOrd/jhDfSEmqLsfg3QReerIKPKghsysOz5cQ== + dependencies: + "@babel/runtime" "^7.14.6" + json5 "^2.2.0" + configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -8046,7 +8068,7 @@ console-browserify@^1.1.0: console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== "consolidated-events@^1.1.0 || ^2.0.0", "consolidated-events@^1.1.1 || ^2.0.0": version "2.0.2" @@ -8056,12 +8078,12 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control- constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== content-disposition@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== content-disposition@0.5.4: version "0.5.4" @@ -8167,12 +8189,12 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== copy-concurrently@^1.0.0: version "1.0.5" @@ -8189,21 +8211,26 @@ copy-concurrently@^1.0.0: copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== copy-text-to-clipboard@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== -copy-to-clipboard@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" - integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== +copy-webpack-plugin@^10.2.4: + version "10.2.4" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" + integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg== dependencies: - toggle-selection "^1.0.6" + fast-glob "^3.2.7" + glob-parent "^6.0.1" + globby "^12.0.2" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" -copy-webpack-plugin@^9.0.0, copy-webpack-plugin@^9.0.1: +copy-webpack-plugin@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz#2d2c460c4c4695ec0a58afb2801a1205256c4e6b" integrity sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA== @@ -8215,61 +8242,48 @@ copy-webpack-plugin@^9.0.0, copy-webpack-plugin@^9.0.1: schema-utils "^3.1.1" serialize-javascript "^6.0.0" -core-js-compat@^3.20.2, core-js-compat@^3.21.0, core-js-compat@^3.8.1: - version "3.21.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82" - integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g== +core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.8.1: + version "3.22.7" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.7.tgz#8359eb66ecbf726dd0cfced8e48d5e73f3224239" + integrity sha512-uI9DAQKKiiE/mclIC5g4AjRpio27g+VMRhe6rQoz+q4Wm4L6A/fJhiLtBw+sfOpDG9wZ3O0pxIw7GbfOlBgjOA== dependencies: - browserslist "^4.19.1" + browserslist "^4.20.3" semver "7.0.0" -core-js-pure@^3.20.2, core-js-pure@^3.8.1, core-js-pure@^3.8.2: - version "3.21.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" - integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== +core-js-pure@^3.20.2, core-js-pure@^3.8.1: + version "3.22.7" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.22.7.tgz#f58489d9b309fa7b26486a0f70d4ec19a418084e" + integrity sha512-wTriFxiZI+C8msGeh7fJcbC/a0V8fdInN1oS2eK79DMBGs8iIJiXhtFJCiT3rBa8w6zroHWW3p8ArlujZ/Mz+w== -core-js@^2.5.7, core-js@^2.6.5: +core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.12: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.0.4, core-js@^3.14.0, core-js@^3.16.1, core-js@^3.2.1, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3, core-js@^3.9.1: - version "3.21.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" - integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== +core-js@^3.0.4, core-js@^3.16.1, core-js@^3.2.1, core-js@^3.22.3, core-js@^3.6.5, core-js@^3.8.2, core-js@^3.8.3: + version "3.22.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.7.tgz#8d6c37f630f6139b8732d10f2c114c3f1d00024f" + integrity sha512-Jt8SReuDKVNZnZEzyEQT5eK6T2RRCXkfTq7Lo09kpm+fHjgGewSbNjV+Wt4yZMhPDdzz2x1ulI5z/w4nxpBseg== core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cornerstone-core@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/cornerstone-core/-/cornerstone-core-2.6.0.tgz#6f7d97fe98ed462254d55557ffa13568592c211c" - integrity sha512-3sMy4Q2UZ9H4f2rYRAd3s4cntDutwSZQ3QlcYgsahoUxInb68MdmeLOKe8yyguwRT9LJ4CbgvOr2WsS8dOba4g== - -cornerstone-math@0.1.9, cornerstone-math@^0.1.9: +cornerstone-math@^0.1.9: version "0.1.9" resolved "https://registry.yarnpkg.com/cornerstone-math/-/cornerstone-math-0.1.9.tgz#7ce5509e8b9f465b01f7c548470725e7569859fc" integrity sha512-NxdooV73asEQgav1S+0e+a4K+W3CXJdLXyFkVN24qqCtmIpzZzwtw3F9KWPCekzSAJmbhtQ3HicOQj3d4vRtuw== -cornerstone-tools@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/cornerstone-tools/-/cornerstone-tools-6.0.2.tgz#6caa22865139c50c31652c9e39dcc611f159d112" - integrity sha512-FDBRglsYHh248KZYc0+1/FZZOis9ZVWQZ/glaa0qE6Nvf3ReFr5ApJh7S7FnG9Nui1rhosiGqLpg3+59ouQtPA== - dependencies: - "@babel/runtime" "7.1.2" - cornerstone-math "0.1.9" - -cornerstone-wado-image-loader@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.0.4.tgz#4f7f3e3a2e123ab59effe28045394ec24101dc59" - integrity sha512-AdroSRMPPEZLCOeXIrv2kQ0Bz+dr2BQbUR1QN0zOOlsaO2TQQBD+YdgXVqFs5XIAtZSs9+9fSCXlnGc71qLFlA== +cornerstone-wado-image-loader@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.1.5.tgz#1e120564e887bf85b03d4a7f90c6aaea5fd5acf2" + integrity sha512-N+CqivL+KNnVzftUqhwtKa7ULvdO96Bo3sTd4dBu05b57FSKoyAjFQ40md/UzbwzC3hEdVy94rh1TVMPRdMN4w== dependencies: "@cornerstonejs/codec-charls" "^0.1.1" "@cornerstonejs/codec-libjpeg-turbo-8bit" "^0.0.7" @@ -8277,13 +8291,16 @@ cornerstone-wado-image-loader@4.0.4: dicom-parser "^1.8.9" pako "^2.0.4" -cors@^2.8.4: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== +cornerstone-wado-image-loader@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.2.0.tgz#c2fd715d20c69310c6d9434d6673aa1ccab1f77a" + integrity sha512-J7FQcJlBaLHcbc8qj9sHW5w039w3s+i2kU8wK2Yghn1rnLcOay8N8pr+aEYua3XT04iD9GOj5NyikSiplITZWQ== dependencies: - object-assign "^4" - vary "^1" + "@cornerstonejs/codec-charls" "^0.1.1" + "@cornerstonejs/codec-libjpeg-turbo-8bit" "^0.0.7" + "@cornerstonejs/codec-openjpeg" "^0.1.0" + dicom-parser "^1.8.9" + pako "^2.0.4" cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.1: version "5.2.1" @@ -8306,7 +8323,7 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -cosmiconfig@^7.0.0: +cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== @@ -8395,19 +8412,10 @@ cross-fetch@^3.1.5: dependencies: node-fetch "2.6.7" -cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - cross-spawn@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" - integrity sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE= + integrity sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA== dependencies: lru-cache "^4.0.1" which "^1.2.9" @@ -8415,7 +8423,7 @@ cross-spawn@^4.0.2: cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= + integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== dependencies: lru-cache "^4.0.1" shebang-command "^1.2.0" @@ -8432,6 +8440,15 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -8469,7 +8486,7 @@ css-blank-pseudo@^3.0.3: css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= + integrity sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q== css-declaration-sorter@^4.0.1: version "4.0.1" @@ -8510,7 +8527,7 @@ css-loader@^3.2.0, css-loader@^3.6.0: schema-utils "^2.7.0" semver "^6.3.0" -css-loader@^5.0.1, css-loader@^5.1.1: +css-loader@^5.0.1: version "5.2.7" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== @@ -8526,7 +8543,21 @@ css-loader@^5.0.1, css-loader@^5.1.1: schema-utils "^3.0.0" semver "^7.3.5" -css-minimizer-webpack-plugin@^3.0.1: +css-loader@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.7" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.5" + +css-minimizer-webpack-plugin@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== @@ -8569,10 +8600,21 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + integrity sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA== dependencies: boolbase "~1.0.0" css-what "2.1" @@ -8610,15 +8652,15 @@ css-what@^3.2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== -css-what@^6.0.1: +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -cssdb@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-6.5.0.tgz#61264b71f29c834f09b59cb3e5b43c8226590122" - integrity sha512-Rh7AAopF2ckPXe/VBcoUS9JrCZNSyc60+KpgE6X25vpVxA32TmiqvExjkfhwP4wGSb6Xe8Z/JIyGqwgx/zZYFA== +cssdb@^6.6.2: + version "6.6.2" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-6.6.2.tgz#6c1c1777483c909a8fc64f296a51546136b35f45" + integrity sha512-w08LaP+DRoPlw4g4LSUp+EWRrWTPlrzWREcU7/6IeMfL7tPR2P9oeQ1G+pxyfMmLWBNDwqHWa6kxiuGMLb71EA== cssesc@^3.0.0: version "3.0.0" @@ -8628,15 +8670,15 @@ cssesc@^3.0.0: cssfontparser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" - integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M= + integrity sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg== -cssnano-preset-advanced@^5.1.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.3.tgz#848422118d7a62b5b29a53edc160f58c7f7f7539" - integrity sha512-AB9SmTSC2Gd8T7PpKUsXFJ3eNsg7dc4CTZ0+XAJ29MNxyJsrCEk7N1lw31bpHrsQH2PVJr21bbWgGAfA9j0dIA== +cssnano-preset-advanced@^5.3.3: + version "5.3.6" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.6.tgz#6c995a86cecc9e6472bf6d120e5517231ed527dc" + integrity sha512-OZHsytu16eStRVrIY3wmPQqhJMaI0+O3raU4JHoKV3uuQYEeQek/FJVUIvYXD55hWR6OjCMyKYNRDw+k3/xgUw== dependencies: autoprefixer "^10.3.7" - cssnano-preset-default "^5.2.7" + cssnano-preset-default "^5.2.10" postcss-discard-unused "^5.1.0" postcss-merge-idents "^5.1.1" postcss-reduce-idents "^5.2.0" @@ -8678,26 +8720,26 @@ cssnano-preset-default@^4.0.8: postcss-svgo "^4.0.3" postcss-unique-selectors "^4.0.1" -cssnano-preset-default@^5.2.7: - version "5.2.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.7.tgz#791e3603fb8f1b46717ac53b47e3c418e950f5f3" - integrity sha512-JiKP38ymZQK+zVKevphPzNSGHSlTI+AOwlasoSRtSVMUU285O7/6uZyd5NbW92ZHp41m0sSHe6JoZosakj63uA== +cssnano-preset-default@^5.2.10: + version "5.2.10" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.10.tgz#6dfffe6cc3b13f3bb356a42c49a334a98700ef45" + integrity sha512-H8TJRhTjBKVOPltp9vr9El9I+IfYsOMhmXdK0LwdvwJcxYX9oWkY7ctacWusgPWAgQq1vt/WO8v+uqpfLnM7QA== dependencies: css-declaration-sorter "^6.2.2" cssnano-utils "^3.1.0" postcss-calc "^8.2.3" postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.0" - postcss-discard-comments "^5.1.1" + postcss-convert-values "^5.1.2" + postcss-discard-comments "^5.1.2" postcss-discard-duplicates "^5.1.0" postcss-discard-empty "^5.1.1" postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.4" - postcss-merge-rules "^5.1.1" + postcss-merge-longhand "^5.1.5" + postcss-merge-rules "^5.1.2" postcss-minify-font-values "^5.1.0" postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.2" - postcss-minify-selectors "^5.2.0" + postcss-minify-params "^5.1.3" + postcss-minify-selectors "^5.2.1" postcss-normalize-charset "^5.1.0" postcss-normalize-display-values "^5.1.0" postcss-normalize-positions "^5.1.0" @@ -8716,12 +8758,12 @@ cssnano-preset-default@^5.2.7: cssnano-util-get-arguments@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + integrity sha512-6RIcwmV3/cBMG8Aj5gucQRsJb4vv4I4rn6YjPbVWd5+Pn/fuG+YseGvXGk00XLkoZkaj31QOD7vMUpNPC4FIuw== cssnano-util-get-match@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + integrity sha512-JPMZ1TSMRUPVIqEalIBNoBtAYbi8okvcFns4O0YIhcdGebeYZK7dMyHJiQ6GqNBA9kE0Hym4Aqym5rPdsV/4Cw== cssnano-util-raw-cache@^4.0.1: version "4.0.1" @@ -8750,12 +8792,12 @@ cssnano@^4.1.10: is-resolvable "^1.0.0" postcss "^7.0.0" -cssnano@^5.0.4, cssnano@^5.0.6: - version "5.1.7" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.7.tgz#99858bef6c76c9240f0cdc9239570bc7db8368be" - integrity sha512-pVsUV6LcTXif7lvKKW9ZrmX+rGRzxkEdJuVJcp5ftUjWITgwam5LMZOgaTvUrWPkcORBey6he7JKb4XAJvrpKg== +cssnano@^5.0.6, cssnano@^5.1.7: + version "5.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.10.tgz#fc6ddd9a4d7d238f320634326ed814cf0abf6e1c" + integrity sha512-ACpnRgDg4m6CZD/+8SgnLcGCgy6DDGdkMbOawwdvVxNietTNLe/MtWcenp6qT0PRt5wzhGl6/cjMWCdhKXC9QA== dependencies: - cssnano-preset-default "^5.2.7" + cssnano-preset-default "^5.2.10" lilconfig "^2.0.3" yaml "^1.10.2" @@ -8769,7 +8811,7 @@ csso@^4.0.2, csso@^4.2.0: csso@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" - integrity sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U= + integrity sha512-FmCI/hmqDeHHLaIQckMhMZneS84yzUZdrWDAvJVVxOwcKE1P1LF9FGmzr1ktIQSxOw6fl3PaQsmfg+GN+VvR3w== dependencies: clap "^1.0.9" source-map "^0.5.3" @@ -8792,73 +8834,124 @@ csstype@^2.5.7: integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA== csstype@^3.0.2: - version "3.0.11" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" - integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" + integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + integrity sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng== dependencies: array-find-index "^1.0.1" cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + integrity sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A== cypress-file-upload@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-3.5.3.tgz#cd706485de3fb2cbd4a8c2dd90fe96d537bb4311" integrity sha512-S/czzqAj1BYz6Xxnfpx2aSc6hXsj76fd8/iuycJ2RxoxCcQMliw8eQV0ugzVlkzr1GD5dKGviNFGYqv3nRJ+Tg== -cypress@^6.4.0: - version "6.9.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.9.1.tgz#ce1106bfdc47f8d76381dba63f943447883f864c" - integrity sha512-/RVx6sOhsyTR9sd9v0BHI4tnDZAhsH9rNat7CIKCUEr5VPWxyfGH0EzK4IHhAqAH8vjFcD4U14tPiJXshoUrmQ== +cypress@^9.5.4: + version "9.7.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.7.0.tgz#bf55b2afd481f7a113ef5604aa8b693564b5e744" + integrity sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q== dependencies: - "@cypress/listr-verbose-renderer" "^0.4.1" - "@cypress/request" "^2.88.5" + "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" - "@types/node" "12.12.50" - "@types/sinonjs__fake-timers" "^6.0.1" + "@types/node" "^14.14.31" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" - arch "^2.1.2" - blob-util "2.0.2" + arch "^2.2.0" + blob-util "^2.0.2" bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" - cli-table3 "~0.6.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" - dayjs "^1.9.3" - debug "4.3.2" - eventemitter2 "^6.4.2" - execa "^4.0.2" + dayjs "^1.10.4" + debug "^4.3.2" + enquirer "^2.3.6" + eventemitter2 "^6.4.3" + execa "4.1.0" executable "^4.1.1" - extract-zip "^1.7.0" - fs-extra "^9.0.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" getos "^3.2.1" - is-ci "^2.0.0" - is-installed-globally "^0.3.2" + is-ci "^3.0.0" + is-installed-globally "~0.4.0" lazy-ass "^1.6.0" - listr "^0.14.3" - lodash "^4.17.19" + listr2 "^3.8.3" + lodash "^4.17.21" log-symbols "^4.0.0" - minimist "^1.2.5" - moment "^2.29.1" + minimist "^1.2.6" ospath "^1.2.2" - pretty-bytes "^5.4.1" - ramda "~0.27.1" + pretty-bytes "^5.6.0" + proxy-from-env "1.0.0" request-progress "^3.0.0" - supports-color "^7.2.0" + semver "^7.3.2" + supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" - url "^0.11.0" yauzl "^2.10.0" +"d3-array@2 - 3", "d3-array@2.10.0 - 3": + version "3.1.6" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.1.6.tgz#0342c835925826f49b4d16eb7027aec334ffc97d" + integrity sha512-DCbBBNuKOeiR9h04ySRBMW52TFVc91O9wJziuyXw6Ztmy8D3oZbmCkOO3UHKC7ceNJsN2Mavo9+vwV8EAEUXzA== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +"d3-interpolate@1.2.0 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-scale@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + dependencies: + d3-array "2.10.0 - 3" + d3-format "1 - 3" + d3-interpolate "1.2.0 - 3" + d3-time "2.1.1 - 3" + d3-time-format "2 - 4" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3": + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" + integrity sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ== + dependencies: + d3-array "2 - 3" + damerau-levenshtein@^1.0.7: version "1.0.8" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" @@ -8867,14 +8960,14 @@ damerau-levenshtein@^1.0.7: dargs@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17" - integrity sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc= + integrity sha512-jyweV/k0rbv2WK4r9KLayuBrSh2Py0tNmV7LBoSMH4hMQyrG8OPyIOWB2VEx4DJKXWmK4lopYMVvORlDt2S8Aw== dependencies: number-is-nan "^1.0.0" dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== dependencies: assert-plus "^1.0.0" @@ -8897,35 +8990,31 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== -date-fns@^2.23.0: - version "2.28.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" - integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== - dateformat@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.9.3: - version "1.11.0" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.0.tgz#009bf7ef2e2ea2d5db2e6583d2d39a4b5061e805" - integrity sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug== +dayjs@^1.10.4: + version "1.11.2" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5" + integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw== -dcmjs@0.16.1: - version "0.16.1" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.1.tgz#76bc61cbdf2c58a2f54990080729ca2d7ee19543" - integrity sha512-t6vsKi5QXzwX1fwnHY2hdU99FfyzmK8aO0OSBDH6XvJNrBp2A6HpoVWbucybOy8eStIPDqs4v0FGP/m39cTCRA== +dcmjs@^0.24.5: + version "0.24.6" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.24.6.tgz#436d00361fb8d4286e68e2b0cab15939fa3acafb" + integrity sha512-ts/DigszrYXMOmYLRVlik4Z6Oq0fb4ykrveFkcIPclTk/uoND0uwbhO4IPMNnuhGGwaVFF6uXQISY+kgbpjn2g== dependencies: - "@babel/polyfill" "^7.8.3" - "@babel/runtime" "^7.8.4" + "@babel/runtime-corejs2" "^7.17.8" + gl-matrix "^3.1.0" + lodash.clonedeep "^4.5.0" loglevelnext "^3.0.1" ndarray "^1.0.19" debug-log@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" - integrity sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8= + integrity sha512-gV/pe1YIaKNgLYnd1g9VNW80tcb7oV5qvNUxG7NM8rbDpnl6RGunzlAtlGSb0wEs3nesu2vHNiX9TSsZ+Y+RjA== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" @@ -8941,13 +9030,6 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" @@ -8962,15 +9044,22 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" - integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw== decamelize-keys@^1.0.0, decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= + integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg== dependencies: decamelize "^1.1.0" map-obj "^1.0.0" @@ -8978,31 +9067,31 @@ decamelize-keys@^1.0.0, decamelize-keys@^1.1.0: decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== dependencies: mimic-response "^1.0.0" -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== dependencies: - mimic-response "^2.0.0" + mimic-response "^3.1.0" dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== deep-equal@^1.0.1: version "1.1.1" @@ -9036,18 +9125,19 @@ deepmerge@^1.5.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753" integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ== -deepmerge@^4.0.0, deepmerge@^4.2.2: +deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -default-gateway@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" - integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== +default-browser-id@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-1.0.4.tgz#e59d09a5d157b828b876c26816e61c3d2a2c203a" + integrity sha512-qPy925qewwul9Hifs+3sx1ZYn14obHxpkX+mPD369w4Rzg+YkJBgi3SOvwUq81nWSjqGUegIgEPwD8u+HUnxlw== dependencies: - execa "^1.0.0" - ip-regex "^2.1.0" + bplist-parser "^0.1.0" + meow "^3.1.0" + untildify "^2.0.0" default-gateway@^6.0.3: version "6.0.3" @@ -9059,7 +9149,7 @@ default-gateway@^6.0.3: defaults@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA== dependencies: clone "^1.0.2" @@ -9073,24 +9163,25 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.1.2, define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== +define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== dependencies: - object-keys "^1.0.12" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== dependencies: is-descriptor "^0.1.0" define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== dependencies: is-descriptor "^1.0.0" @@ -9105,7 +9196,7 @@ define-property@^2.0.2: defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== deglob@^3.1.0: version "3.1.0" @@ -9147,9 +9238,9 @@ del@^5.0.0: slash "^3.0.0" del@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" - integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== dependencies: globby "^11.0.1" graceful-fs "^4.2.4" @@ -9163,12 +9254,12 @@ del@^6.0.0: delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== depd@2.0.0: version "2.0.0" @@ -9178,7 +9269,7 @@ depd@2.0.0: depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" @@ -9198,11 +9289,6 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - detab@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" @@ -9210,27 +9296,41 @@ detab@2.0.4: dependencies: repeat-string "^1.5.4" +detect-gpu@^4.0.16, detect-gpu@^4.0.7: + version "4.0.24" + resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-4.0.24.tgz#3b8e4e56a5ed0b764884a10c537efd033ce31b9b" + integrity sha512-lSL8+a+aCvjsqoHKAHF1rcMOkczYk2lUVimgndZqxL/lf1zMtjcGX8ArF8cJqn5OW4Ga2xi5wY2CV/zLYFYl9Q== + dependencies: + webgl-constants "^1.1.1" + detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= + integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +detect-libc@^2.0.0, detect-libc@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= + integrity sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg== detect-node@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -detect-port-alt@1.1.6: +detect-package-manager@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-package-manager/-/detect-package-manager-2.0.1.tgz#6b182e3ae5e1826752bfef1de9a7b828cffa50d8" + integrity sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A== + dependencies: + execa "^5.1.1" + +detect-port-alt@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== @@ -9247,18 +9347,13 @@ detect-port@^1.3.0: debug "^2.6.0" detective@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" - integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + version "5.2.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== dependencies: - acorn-node "^1.6.1" + acorn-node "^1.8.2" defined "^1.0.0" - minimist "^1.1.1" - -devtools-protocol@0.0.818844: - version "0.0.818844" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.818844.tgz#d1947278ec85b53e4c8ca598f607a28fa785ba9e" - integrity sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg== + minimist "^1.2.6" dezalgo@^1.0.0: version "1.0.4" @@ -9288,6 +9383,11 @@ diff-sequences@^24.9.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -9333,7 +9433,7 @@ dnd-core@14.0.0: dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== dns-packet@^1.3.1: version "1.3.4" @@ -9343,10 +9443,17 @@ dns-packet@^1.3.1: ip "^1.1.0" safe-buffer "^5.0.1" +dns-packet@^5.2.2: + version "5.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.3.1.tgz#eb94413789daec0f0ebe2fcc230bdc9d7c91b43d" + integrity sha512-spBwIj0TK0Ey3666GwIdWVfUpLyubpU53BTCu8iPn4r4oXd9O14Hjg3EHw3ts2oed77/SeckunUYCyRlSngqHw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + dns-txt@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + integrity sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ== dependencies: buffer-indexof "^1.0.0" @@ -9402,14 +9509,23 @@ dom-serializer@0: entities "^2.0.0" dom-serializer@^1.0.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" - integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== dependencies: domelementtype "^2.0.1" domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + dom-serializer@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" @@ -9418,6 +9534,11 @@ dom-serializer@~0.1.0: domelementtype "^1.3.0" entities "^1.1.1" +dom-to-image@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/dom-to-image/-/dom-to-image-2.6.0.tgz#8a503608088c87b1c22f9034ae032e1898955867" + integrity sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA== + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" @@ -9433,10 +9554,10 @@ domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" - integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== domexception@^1.0.1: version "1.0.1" @@ -9459,10 +9580,17 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + integrity sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw== dependencies: dom-serializer "0" domelementtype "1" @@ -9484,6 +9612,15 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" + integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.1" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -9549,21 +9686,10 @@ dotenv@^8.0.0, dotenv@^8.1.0, dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== -downshift@^6.0.15: - version "6.1.7" - resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.1.7.tgz#fdb4c4e4f1d11587985cd76e21e8b4b3fa72e44c" - integrity sha512-cVprZg/9Lvj/uhYRxELzlu1aezRcgPWBjTvspiGTVEU64gF5pRdSRKFVLcxqsZC637cLAGMbL40JavEfWnqgNg== - dependencies: - "@babel/runtime" "^7.14.8" - compute-scroll-into-view "^1.0.17" - prop-types "^15.7.2" - react-is "^17.0.2" - tslib "^2.3.0" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + integrity sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA== duplexer@^0.1.1, duplexer@^0.1.2, duplexer@~0.1.1: version "0.1.2" @@ -9580,10 +9706,15 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== dependencies: jsbn "~0.1.0" safer-buffer "^2.1.0" @@ -9591,31 +9722,24 @@ ecc-jsbn@~0.1.1: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== ejs@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" - integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: - jake "^10.6.1" + jake "^10.8.5" -electron-to-chromium@^1.3.564, electron-to-chromium@^1.4.84: - version "1.4.104" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.104.tgz#60973b0a7d398efa877196e8ccb0c93d48b918d8" - integrity sha512-2kjoAyiG7uMyGRM9mx25s3HAzmQG2ayuYXxsFmYugHSDcwxREgLtscZvbL1JcW9S/OemeQ3f/SG6JhDwpnCclQ== +electron-to-chromium@^1.4.118: + version "1.4.143" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.143.tgz#10f1bb595ad6cd893c05097039c685dcf5c8e30c" + integrity sha512-2hIgvu0+pDfXIqmVmV5X6iwMjQ2KxDsWKwM+oI1fABEOy/Dqmll0QJRmIQ3rm+XaoUa/qKrmy5h7LSTFQ6Ldzg== elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" - integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= - -element-resize-detector@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz#3e6c5982dd77508b5fa7e6d5c02170e26325c9b1" - integrity sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg== - dependencies: - batch-processor "1.0.0" + integrity sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ== elliptic@^6.5.3: version "6.5.4" @@ -9664,15 +9788,10 @@ emotion-theming@^10.0.27: "@emotion/weak-memoize" "0.2.5" hoist-non-react-statics "^3.3.0" -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== encoding@^0.1.11: version "0.1.13" @@ -9706,15 +9825,15 @@ enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^5.9.2: - version "5.9.2" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" - integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== +enhanced-resolve@^5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" + integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: +enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -9731,6 +9850,16 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" + integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== + +entities@^4.2.0, entities@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.3.0.tgz#62915f08d67353bb4eb67e3d62641a4059aec656" + integrity sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg== + env-paths@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -9752,7 +9881,7 @@ enzyme-shallow-equal@^1.0.0: err-code@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" - integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= + integrity sha512-CJAN+O0/yA1CKfRn9SXOGctSpEM7DCon/r/5r2eXFMY2zCCJBasFhcM5I+1kh3Ap11FsQCX+vGHceNPvpWKhoA== errno@^0.1.3, errno@~0.1.7: version "0.1.8" @@ -9775,31 +9904,34 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.17.2, es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1: - version "1.19.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.2.tgz#8f7b696d8f15b167ae3640b4060670f3d054143f" - integrity sha512-gfSBJoZdlL2xRiOCy0g8gLMryhoe1TlimjzU99L/31Z8QEGIhVQI+EWwt5lT+AuU9SnorVupXFqqOGqGfsyO6w== +es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.4, es-abstract@^1.19.5, es-abstract@^1.20.0, es-abstract@^1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" + integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + function.prototype.name "^1.1.5" get-intrinsic "^1.1.1" get-symbol-description "^1.0.0" has "^1.0.3" + has-property-descriptors "^1.0.0" has-symbols "^1.0.3" internal-slot "^1.0.3" is-callable "^1.2.4" is-negative-zero "^2.0.2" is-regex "^1.1.4" - is-shared-array-buffer "^1.0.1" + is-shared-array-buffer "^1.0.2" is-string "^1.0.7" is-weakref "^1.0.2" object-inspect "^1.12.0" object-keys "^1.1.1" object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" es-array-method-boxes-properly@^1.0.0: version "1.0.0" @@ -9825,6 +9957,13 @@ es-module-lexer@^0.9.0: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -9835,14 +9974,9 @@ es-to-primitive@^1.2.1: is-symbol "^1.0.2" es5-shim@^4.5.13: - version "4.6.5" - resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.6.5.tgz#2124bb073b7cede2ed23b122a1fd87bb7b0bb724" - integrity sha512-vfQ4UAai8szn0sAubCy97xnZ4sJVDD1gt/Grn736hg8D7540wemIb1YPrYZSTqlM2H69EQX1or4HU/tSwRTI3w== - -es6-promise-pool@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/es6-promise-pool/-/es6-promise-pool-2.5.0.tgz#147c612b36b47f105027f9d2bf54a598a99d9ccb" - integrity sha1-FHxhKza0fxBQJ/nSv1SlmKmdnMs= + version "4.6.7" + resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.6.7.tgz#bc67ae0fc3dd520636e0a1601cc73b450ad3e955" + integrity sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ== es6-promise@^4.0.3: version "4.2.8" @@ -9852,7 +9986,7 @@ es6-promise@^4.0.3: es6-promisify@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== dependencies: es6-promise "^4.0.3" @@ -9861,7 +9995,7 @@ es6-shim@^0.35.5: resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.6.tgz#d10578301a83af2de58b9eadb7c2c9945f7388a0" integrity sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA== -escalade@^3.0.2, escalade@^3.1.1: +escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== @@ -9874,23 +10008,23 @@ escape-goat@^2.0.0: escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: +escape-string-regexp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - escodegen@^1.9.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -10021,34 +10155,42 @@ eslint-plugin-promise@^5.2.0: integrity sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw== eslint-plugin-react-hooks@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.4.0.tgz#71c39e528764c848d8253e1aa2c7024ed505f6c4" - integrity sha512-U3RVIfdzJaeKDQKEJbz5p3NW8/L80PCATJAfuojwbaEL+gBjfGdhUcGde+WGUW46Q5sr/NgxevsIiDtNXrvZaQ== + version "4.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz#5f762dfedf8b2cf431c689f533c9d3fa5dcf25ad" + integrity sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw== eslint-plugin-react@^7.29.4: - version "7.29.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2" - integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ== + version "7.30.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.30.0.tgz#8e7b1b2934b8426ac067a0febade1b13bd7064e3" + integrity sha512-RgwH7hjW48BleKsYyHK5vUAvxtE9SMPDKmcPRQgtRCYaZA0XQPt5FSkrU3nhz5ifzMZcA8opwmRJ2cmOO8tr5A== dependencies: - array-includes "^3.1.4" - array.prototype.flatmap "^1.2.5" + array-includes "^3.1.5" + array.prototype.flatmap "^1.3.0" doctrine "^2.1.0" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" object.entries "^1.1.5" object.fromentries "^2.0.5" - object.hasown "^1.1.0" + object.hasown "^1.1.1" object.values "^1.1.5" prop-types "^15.8.1" resolve "^2.0.0-next.3" semver "^6.3.0" - string.prototype.matchall "^4.0.6" + string.prototype.matchall "^4.0.7" + +eslint-plugin-tsdoc@^0.2.11: + version "0.2.16" + resolved "https://registry.yarnpkg.com/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.16.tgz#a3d31fb9c7955faa3c66a43dd43da7635f1c5e0d" + integrity sha512-F/RWMnyDQuGlg82vQEFHQtGyWi7++XJKdYNn0ulIbyMOFqYIjoJOUdE6olORxgwgLkpJxsCJpJbTHgxJ/ggfXw== + dependencies: + "@microsoft/tsdoc" "0.14.1" + "@microsoft/tsdoc-config" "0.16.1" eslint-scope@3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" - integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= + integrity sha512-ivpbtpUgg9SJS4TLjK7KdcDhqc/E3CGItsvQbBNLkNGUeMhd5qnJcryba/brESS+dg3vrLqPuc/UcS7jRJdN5A== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -10076,6 +10218,13 @@ eslint-utils@^2.0.0, eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" @@ -10086,6 +10235,18 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +eslint-webpack-plugin@^2.5.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-2.6.0.tgz#3bd4ada4e539cb1f6687d2f619073dbb509361cd" + integrity sha512-V+LPY/T3kur5QO3u+1s34VDTcRxjXWPUGM4hlmTb5DwVD0OQz631yGTxJZf4SpAqAjdbBVe978S8BJeHpAdOhQ== + dependencies: + "@types/eslint" "^7.28.2" + arrify "^2.0.1" + jest-worker "^27.3.1" + micromatch "^4.0.4" + normalize-path "^3.0.0" + schema-utils "^3.1.1" + eslint@^7.32.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -10144,9 +10305,9 @@ espree@^7.3.0, espree@^7.3.1: esprima@^2.6.0: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= + integrity sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A== -esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -10194,7 +10355,7 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eta@^1.11.0, eta@^1.12.1: +eta@^1.12.3: version "1.12.3" resolved "https://registry.yarnpkg.com/eta/-/eta-1.12.3.tgz#2982d08adfbef39f9fa50e2fbd42d7337e7338b1" integrity sha512-qHixwbDLtekO/d51Yr4glcaUJCIjGVJyTzuqV4GPlgZo1YpgOKG+avQynErZIYrfM6JIJdtiG2Kox8tbb+DoGg== @@ -10202,7 +10363,7 @@ eta@^1.11.0, eta@^1.12.1: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== eval@^0.1.8: version "0.1.8" @@ -10215,7 +10376,7 @@ eval@^0.1.8: event-stream@=3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" - integrity sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE= + integrity sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g== dependencies: duplexer "~0.1.1" from "~0" @@ -10225,7 +10386,7 @@ event-stream@=3.3.4: stream-combiner "~0.0.4" through "~2.3.1" -eventemitter2@^6.4.2: +eventemitter2@^6.4.3: version "6.4.5" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.5.tgz#97380f758ae24ac15df8353e0cc27f8b95644655" integrity sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw== @@ -10245,13 +10406,6 @@ events@^3.0.0, events@^3.2.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -eventsource@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf" - integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg== - dependencies: - original "^1.0.0" - evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -10265,7 +10419,22 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== -execa@5.1.1, execa@^5.0.0: +execa@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +execa@5.1.1, execa@^5.0.0, execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -10283,7 +10452,7 @@ execa@5.1.1, execa@^5.0.0: execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= + integrity sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw== dependencies: cross-spawn "^5.0.1" get-stream "^3.0.0" @@ -10296,7 +10465,7 @@ execa@^0.7.0: execa@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" - integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo= + integrity sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA== dependencies: cross-spawn "^5.0.1" get-stream "^3.0.0" @@ -10334,21 +10503,6 @@ execa@^2.0.3: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^4.0.2: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - execa@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-6.1.0.tgz#cea16dee211ff011246556388effa0818394fb20" @@ -10373,28 +10527,18 @@ executable@^4.1.1: exenv@^1.2.0: version "1.2.2" - resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" - integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= - -exif-parser@^0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922" - integrity sha1-WKnS1ywCwfbwKg70qRZicrd2CSI= - -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" - integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== dependencies: debug "^2.3.3" define-property "^0.2.5" @@ -10421,38 +10565,39 @@ expect@^24.9.0: jest-message-util "^24.9.0" jest-regex-util "^24.9.0" -express@^4.16.3, express@^4.17.1: - version "4.17.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" - integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== +express@^4.17.1, express@^4.17.3: + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.2" + body-parser "1.20.0" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.2" + cookie "0.5.0" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.2" + depd "2.0.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "~1.1.2" + finalhandler "1.2.0" fresh "0.5.2" + http-errors "2.0.0" merge-descriptors "1.0.1" methods "~1.1.2" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.9.7" + qs "6.10.3" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.17.2" - serve-static "1.14.2" + send "0.18.0" + serve-static "1.15.0" setprototypeof "1.2.0" - statuses "~1.5.0" + statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -10460,14 +10605,14 @@ express@^4.16.3, express@^4.17.1: extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== dependencies: is-extendable "^0.1.0" extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== dependencies: assign-symbols "^1.0.0" is-extendable "^1.0.1" @@ -10500,22 +10645,17 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-stack@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-2.0.0.tgz#11367bc865bfcd9bc0db3123e5edb57786f11f9b" - integrity sha512-AEo4zm+TenK7zQorGK1f9mJ8L14hnTDi2ZQPR+Mub1NX8zimka1mXpV5LpH8x9HoUmFSHZCfLHqWvp0Y4FxxzQ== - -extract-zip@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" - integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== +extract-css-chunks-webpack-plugin@^4.5.4: + version "4.9.0" + resolved "https://registry.yarnpkg.com/extract-css-chunks-webpack-plugin/-/extract-css-chunks-webpack-plugin-4.9.0.tgz#da5e6b1d8b39a398c817ffc98550f4ccb6d795e1" + integrity sha512-HNuNPCXRMqJDQ1OHAUehoY+0JVCnw9Y/H22FQzYVwo8Ulgew98AGDu0grnY5c7xwiXHjQa6yJ/1dxLCI/xqTyQ== dependencies: - concat-stream "^1.6.2" - debug "^2.6.9" - mkdirp "^0.5.4" - yauzl "^2.10.0" + loader-utils "^2.0.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" -extract-zip@^2.0.0: +extract-zip@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -10529,7 +10669,7 @@ extract-zip@^2.0.0: extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== extsprintf@^1.2.0: version "1.4.1" @@ -10539,7 +10679,7 @@ extsprintf@^1.2.0: fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + integrity sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -10563,7 +10703,7 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" -fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.5, fast-glob@^3.2.7, fast-glob@^3.2.9: +fast-glob@^3.0.3, fast-glob@^3.2.5, fast-glob@^3.2.7, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -10587,23 +10727,16 @@ fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fast-levenshtein@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz#37b899ae47e1090e40e3fd2318e4d5f0142ca912" - integrity sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ== - dependencies: - fastest-levenshtein "^1.0.7" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-url-parser@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= + integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== dependencies: punycode "^1.3.2" -fastest-levenshtein@^1.0.12, fastest-levenshtein@^1.0.7: +fastest-levenshtein@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== @@ -10622,7 +10755,7 @@ fault@^1.0.0: dependencies: format "^0.2.0" -faye-websocket@^0.11.3, faye-websocket@^0.11.4: +faye-websocket@^0.11.3: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== @@ -10664,15 +10797,10 @@ fbjs@^3.0.0, fbjs@^3.0.1: fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== dependencies: pend "~1.2.0" -fecha@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.1.tgz#0a83ad8f86ef62a091e22bb5a039cd03d23eecce" - integrity sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q== - feed@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e" @@ -10688,6 +10816,11 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" +fetch-retry@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.2.tgz#4c55663a7c056cb45f182394e479464f0ff8f3e3" + integrity sha512-57Hmu+1kc6pKFUGVIobT7qw3NeAzY/uNN26bSevERLVvf6VGFR/ooDCOFBHMNDgAxBiU2YJq1D0vFzc6U1DcPw== + figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -10696,7 +10829,7 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= + integrity sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ== dependencies: escape-string-regexp "^1.0.5" object-assign "^4.1.0" @@ -10704,11 +10837,11 @@ figures@^1.7.0: figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== dependencies: escape-string-regexp "^1.0.5" -figures@^3.0.0: +figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -10738,18 +10871,12 @@ file-selector@^0.1.12: tslib "^2.0.1" file-system-cache@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" - integrity sha1-hCWbNqK7uNPW6xAh0xMv/mTP/08= + version "1.1.0" + resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.1.0.tgz#984de17b976b75a77a27e08d6828137c1aa80fa1" + integrity sha512-IzF5MBq+5CR0jXx5RxPe4BICl/oEhBSXKaL9fLhAXrIfIUS77Hr4vzrYyqYMHN6uTt+BOqi3fDCTjjEBCjERKw== dependencies: - bluebird "^3.3.5" - fs-extra "^0.30.0" - ramda "^0.21.0" - -file-type@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18" - integrity sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw== + fs-extra "^10.1.0" + ramda "^0.28.0" file-uri-to-path@1.0.0: version "1.0.0" @@ -10757,21 +10884,21 @@ file-uri-to-path@1.0.0: integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== filelist@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" - integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== dependencies: - minimatch "^3.0.4" + minimatch "^5.0.1" -filesize@6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" - integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== dependencies: extend-shallow "^2.0.1" is-number "^3.0.0" @@ -10788,19 +10915,19 @@ fill-range@^7.0.1: filter-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== dependencies: debug "2.6.9" encodeurl "~1.0.2" escape-html "~1.0.3" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" - statuses "~1.5.0" + statuses "2.0.1" unpipe "~1.0.0" find-babel-config@^1.1.0: @@ -10834,18 +10961,10 @@ find-root@^1.0.0, find-root@^1.1.0: resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== -find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== dependencies: path-exists "^2.0.0" pinkie-promise "^2.0.0" @@ -10853,7 +10972,7 @@ find-up@^1.0.0: find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== dependencies: locate-path "^2.0.0" @@ -10864,6 +10983,14 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -10901,20 +11028,10 @@ flux@^4.0.1: fbemitter "^3.0.0" fbjs "^3.0.1" -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -follow-redirects@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6" - integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== - -follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.8: - version "1.14.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" - integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.7, follow-redirects@^1.14.8: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== for-each@^0.3.3: version "0.3.3" @@ -10926,17 +11043,7 @@ for-each@^0.3.3: for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= - -foreachasync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6" - integrity sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY= + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== foreground-child@^2.0.0: version "2.0.0" @@ -10949,9 +11056,9 @@ foreground-child@^2.0.0: forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== -fork-ts-checker-webpack-plugin@4.1.6, fork-ts-checker-webpack-plugin@^4.1.6: +fork-ts-checker-webpack-plugin@^4.1.6: version "4.1.6" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz#5055c703febcf37fa06405d400c122b905167fc5" integrity sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw== @@ -10964,10 +11071,10 @@ fork-ts-checker-webpack-plugin@4.1.6, fork-ts-checker-webpack-plugin@^4.1.6: tapable "^1.0.0" worker-rpc "^0.1.0" -fork-ts-checker-webpack-plugin@^6.0.4: - version "6.5.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" - integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== +fork-ts-checker-webpack-plugin@^6.0.4, fork-ts-checker-webpack-plugin@^6.5.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340" + integrity sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA== dependencies: "@babel/code-frame" "^7.8.3" "@types/json-schema" "^7.0.5" @@ -11004,7 +11111,7 @@ form-data@~2.3.2: format@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" - integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== formdata-polyfill@^4.0.10: version "4.0.10" @@ -11026,19 +11133,19 @@ fraction.js@^4.2.0: fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== dependencies: map-cache "^0.2.2" fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== dependencies: inherits "^2.0.1" readable-stream "^2.0.0" @@ -11046,34 +11153,23 @@ from2@^2.1.0: from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= + integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g== fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - integrity sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - -fs-extra@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8" - integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag== +fs-extra@^10.0.0, fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^8.1, fs-extra@^8.1.0: +fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== @@ -11082,7 +11178,7 @@ fs-extra@^8.1, fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -11114,7 +11210,7 @@ fs-monkey@1.0.3: fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + integrity sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA== dependencies: graceful-fs "^4.1.2" iferr "^0.1.5" @@ -11124,7 +11220,7 @@ fs-write-stream-atomic@^1.0.8: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^1.2.7: version "1.2.13" @@ -11144,7 +11240,7 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.0, function.prototype.name@^1.1.2: +function.prototype.name@^1.1.0, function.prototype.name@^1.1.2, function.prototype.name@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== @@ -11157,17 +11253,12 @@ function.prototype.name@^1.1.0, function.prototype.name@^1.1.2: functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== functions-have-names@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21" - integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== - -fuse.js@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.6.1.tgz#7de85fdd6e1b3377c23ce010892656385fd9b10c" - integrity sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw== + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== gauge@^3.0.0: version "3.0.2" @@ -11187,7 +11278,7 @@ gauge@^3.0.0: gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg== dependencies: aproba "^1.0.3" console-control-strings "^1.0.0" @@ -11198,11 +11289,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -generic-pool@^3.7.1: - version "3.8.2" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9" - integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg== - genfun@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537" @@ -11240,7 +11326,7 @@ get-package-type@^0.1.0: get-pkg-repo@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d" - integrity sha1-xztInAbYDMVTbCyFP54FIyBWly0= + integrity sha512-xPCyvcEOxCJDxhBfXDNH+zA7mIRGb2aY1gIUJWsZkpJbp1BLHl+/Sycg26Dv+ZbZAJkO61tzbBtqHUi30NGBvg== dependencies: hosted-git-info "^2.1.4" meow "^3.3.0" @@ -11256,7 +11342,7 @@ get-port@^4.2.0: get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + integrity sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw== get-stdin@^7.0.0: version "7.0.0" @@ -11266,7 +11352,7 @@ get-stdin@^7.0.0: get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" @@ -11298,7 +11384,7 @@ get-symbol-description@^1.0.0: get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== getos@^3.2.1: version "3.2.1" @@ -11310,18 +11396,10 @@ getos@^3.2.1: getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== dependencies: assert-plus "^1.0.0" -gifwrap@^0.9.2: - version "0.9.4" - resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.4.tgz#f4eb6169ba027d61df64aafbdcb1f8ae58ccc0c5" - integrity sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ== - dependencies: - image-q "^4.0.0" - omggif "^1.0.10" - git-raw-commits@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.0.tgz#d92addf74440c14bcc5c83ecce3fb7f8a79118b5" @@ -11336,7 +11414,7 @@ git-raw-commits@2.0.0: git-remote-origin-url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" - integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8= + integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw== dependencies: gitconfiglocal "^1.0.0" pify "^2.3.0" @@ -11367,16 +11445,16 @@ git-url-parse@^11.1.2: gitconfiglocal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" - integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s= + integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ== dependencies: ini "^1.3.2" github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" - integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== -github-slugger@^1.0.0, github-slugger@^1.3.0: +github-slugger@^1.0.0, github-slugger@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.4.0.tgz#206eb96cdb22ee56fdc53a28d5a302338463444e" integrity sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ== @@ -11386,10 +11464,15 @@ gitignore@^0.7.0: resolved "https://registry.yarnpkg.com/gitignore/-/gitignore-0.7.0.tgz#062173df62344a572dfd76d98b59c45108562739" integrity sha512-6iE891OyeYQYVvdoWI/hcxDWJ0sOngSpIRadxLoYbsnZdqWIUsEfx+IrOpW3d/zWBA/eKYvs6ZZx6ogz2wEGoQ== +gl-matrix@3.4.3, gl-matrix@^3.1.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9" + integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== dependencies: is-glob "^3.1.0" path-dirname "^1.0.0" @@ -11418,7 +11501,7 @@ glob-promise@^3.4.0: glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= + integrity sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig== glob-to-regexp@^0.4.1: version "0.4.1" @@ -11426,14 +11509,14 @@ glob-to-regexp@^0.4.1: integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" @@ -11445,13 +11528,6 @@ global-cache@^1.2.1: define-properties "^1.1.2" is-symbol "^1.0.1" -global-dirs@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" - integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== - dependencies: - ini "1.3.7" - global-dirs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" @@ -11459,7 +11535,7 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" -global-modules@2.0.0: +global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== @@ -11475,7 +11551,7 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global@^4.3.0, global@^4.4.0, global@~4.4.0: +global@^4.3.0, global@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== @@ -11489,31 +11565,19 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.13.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.13.0.tgz#ac32261060d8070e2719dd6998406e27d2b5727b" - integrity sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A== + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" -globalthis@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" - integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== +globalthis@1.0.3, globalthis@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== dependencies: define-properties "^1.1.3" -globby@11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - globby@^10.0.1: version "10.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" @@ -11528,7 +11592,7 @@ globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: +globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -11540,10 +11604,22 @@ globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: merge2 "^1.4.1" slash "^3.0.0" +globby@^12.0.2: + version "12.2.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22" + integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA== + dependencies: + array-union "^3.0.1" + dir-glob "^3.0.1" + fast-glob "^3.2.7" + ignore "^5.1.9" + merge2 "^1.4.1" + slash "^4.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + integrity sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw== dependencies: array-union "^1.0.1" glob "^7.0.3" @@ -11582,7 +11658,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -11600,21 +11676,13 @@ gray-matter@^4.0.3: growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw== gud@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== -gzip-size@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -11625,7 +11693,7 @@ gzip-size@^6.0.0: hammerjs@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" - integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE= + integrity sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ== handle-thing@^2.0.0: version "2.0.1" @@ -11647,7 +11715,7 @@ handlebars@^4.7.6, handlebars@^4.7.7: har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== har-validator@~5.1.3: version "5.1.5" @@ -11670,19 +11738,19 @@ harmony-reflect@^1.4.6: has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + integrity sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== dependencies: ansi-regex "^2.0.0" -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" @@ -11692,10 +11760,17 @@ has-flag@^4.0.0: has-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-glob/-/has-glob-1.0.0.tgz#9aaa9eedbffb1ba3990a7b0010fb678ee0081207" - integrity sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc= + integrity sha512-D+8A457fBShSEI3tFCj65PAbT++5sKiFtdCdOam0gnfBgw9D277OERk+HM9qYJXmdVLZ/znez10SqHN0BBQ50g== dependencies: is-glob "^3.0.0" +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -11711,12 +11786,12 @@ has-tostringtag@^1.0.0: has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== dependencies: get-value "^2.0.3" has-values "^0.1.4" @@ -11725,7 +11800,7 @@ has-value@^0.3.1: has-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== dependencies: get-value "^2.0.6" has-values "^1.0.0" @@ -11734,12 +11809,12 @@ has-value@^1.0.0: has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== has-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== dependencies: is-number "^3.0.0" kind-of "^4.0.0" @@ -11872,18 +11947,11 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^10.1.1, highlight.js@~10.7.0: +highlight.js@^10.4.1, highlight.js@~10.7.0: version "10.7.3" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== -history@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08" - integrity sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg== - dependencies: - "@babel/runtime" "^7.7.6" - history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -11896,7 +11964,7 @@ history@^4.9.0: tiny-warning "^1.0.0" value-equal "^1.0.1" -history@^5.2.0: +history@^5.2.0, history@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== @@ -11906,7 +11974,7 @@ history@^5.2.0: hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" @@ -11934,7 +12002,7 @@ hosted-git-info@^4.0.1: hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== dependencies: inherits "^2.0.1" obuf "^1.0.0" @@ -11944,12 +12012,12 @@ hpack.js@^2.1.6: hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + integrity sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A== hsla-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + integrity sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA== html-encoding-sniffer@^1.0.2: version "1.0.2" @@ -11958,11 +12026,6 @@ html-encoding-sniffer@^1.0.2: dependencies: whatwg-encoding "^1.0.1" -html-entities@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" - integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== - html-entities@^2.1.0, html-entities@^2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" @@ -11973,7 +12036,7 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-minifier-terser@^5.0.1, html-minifier-terser@^5.1.1: +html-minifier-terser@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== @@ -11986,7 +12049,7 @@ html-minifier-terser@^5.0.1, html-minifier-terser@^5.1.1: relateurl "^0.2.7" terser "^4.6.3" -html-minifier-terser@^6.0.2: +html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== @@ -12002,11 +12065,11 @@ html-minifier-terser@^6.0.2: html-parse-stringify2@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a" - integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o= + integrity sha512-wMKQ3aJ/dwXzDHPpA7XgsRXXCkEhHkAF6Ioh7D51lgZO7Qy0LmcFddC9TI/qNQJvSM1KL8KbcR3FtuybsrzFlQ== dependencies: void-elements "^2.0.1" -html-tags@^3.1.0: +html-tags@^3.1.0, html-tags@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961" integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== @@ -12031,7 +12094,7 @@ html-webpack-plugin@^4.0.0: tapable "^1.1.3" util.promisify "1.0.0" -html-webpack-plugin@^5.0.0, html-webpack-plugin@^5.3.2: +html-webpack-plugin@^5.0.0, html-webpack-plugin@^5.3.2, html-webpack-plugin@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== @@ -12064,6 +12127,16 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +htmlparser2@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010" + integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + domutils "^3.0.1" + entities "^4.3.0" + http-cache-semantics@^3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" @@ -12077,18 +12150,7 @@ http-cache-semantics@^4.0.0: http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= - -http-errors@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.1" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== http-errors@2.0.0: version "2.0.0" @@ -12104,7 +12166,7 @@ http-errors@2.0.0: http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== dependencies: depd "~1.1.2" inherits "2.0.3" @@ -12124,20 +12186,10 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-middleware@0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" - integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== - dependencies: - http-proxy "^1.17.0" - is-glob "^4.0.0" - lodash "^4.17.11" - micromatch "^3.1.10" - -http-proxy-middleware@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" - integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg== +http-proxy-middleware@^2.0.0, http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" @@ -12145,7 +12197,7 @@ http-proxy-middleware@^2.0.0: is-plain-obj "^3.0.0" micromatch "^4.0.2" -http-proxy@^1.17.0, http-proxy@^1.18.1: +http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== @@ -12157,7 +12209,7 @@ http-proxy@^1.17.0, http-proxy@^1.18.1: http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== dependencies: assert-plus "^1.0.0" jsprim "^1.2.2" @@ -12175,7 +12227,7 @@ http-signature@~1.3.6: https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== https-proxy-agent@^2.2.3: version "2.2.4" @@ -12185,14 +12237,6 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -12211,7 +12255,7 @@ human-signals@^3.0.1: humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== dependencies: ms "^2.0.0" @@ -12232,11 +12276,6 @@ husky@^3.0.0: run-node "^1.0.0" slash "^3.0.0" -hyperlinker@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" - integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== - i18next-browser-languagedetector@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-3.1.1.tgz#1a0c236d4339476cc3632da60ff947bbc2e3ba3c" @@ -12290,7 +12329,7 @@ idb@^6.1.4: identity-obj-proxy@3.0.x: version "3.0.0" resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" - integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= + integrity sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA== dependencies: harmony-reflect "^1.4.6" @@ -12302,7 +12341,7 @@ ieee754@^1.1.13, ieee754@^1.1.4: iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= + integrity sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA== ignore-walk@^3.0.1: version "3.0.4" @@ -12316,39 +12355,37 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.0, ignore@^5.1.1, ignore@^5.1.4, ignore@^5.2.0: +ignore@^5.0.0, ignore@^5.1.1, ignore@^5.1.8, ignore@^5.1.9, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -image-q@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/image-q/-/image-q-4.0.0.tgz#31e075be7bae3c1f42a85c469b4732c358981776" - integrity sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw== +image-size@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.1.tgz#86d6cfc2b1d19eab5d2b368d4b9194d9e48541c5" + integrity sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ== dependencies: - "@types/node" "16.9.1" + queue "6.0.2" -image-size@^0.8.2: - version "0.8.3" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.8.3.tgz#f0b568857e034f29baffd37013587f2c0cad8b46" - integrity sha512-SMtq1AJ+aqHB45c3FsB4ERK0UCiA2d3H1uq8s+8T0Pf8A3W4teyBQyaFaktH6xvZqh+npwlKU7i4fJo0r7TYTg== - dependencies: - queue "6.0.1" +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -immer@8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" - integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== +immer@^9.0.7: + version "9.0.14" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.14.tgz#e05b83b63999d26382bb71676c9d827831248a48" + integrity sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw== import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg== dependencies: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.2.2, import-fresh@^3.3.0: +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -12359,7 +12396,7 @@ import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.2 import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== import-local@^2.0.0: version "2.0.0" @@ -12380,19 +12417,19 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + integrity sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg== dependencies: repeating "^2.0.0" indent-string@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" - integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= + integrity sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ== indent-string@^4.0.0: version "4.0.0" @@ -12402,27 +12439,27 @@ indent-string@^4.0.0: indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + integrity sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA== infer-owner@^1.0.3, infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== -infima@0.2.0-alpha.26: - version "0.2.0-alpha.26" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.26.tgz#8582d40ef01a09dbbde8f0e574f8c61d6bc0992b" - integrity sha512-0/Dt+89mf8xW+9/hKGmynK+WOAsiy0QydVJL0qie6WK57yGIQv+SjJrhMybKndnmkZBQ+Vlt0tWPnTakx8X2Qw== +infima@0.2.0-alpha.39: + version "0.2.0-alpha.39" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.39.tgz#054b13ac44f3e9a42bc083988f1a1586add2f59c" + integrity sha512-UyYiwD3nwHakGhuOUfpe3baJ8gkiPpRVx4a4sE/Ag+932+Y6swtLsdPoRR8ezhwqGnduzxmFkjumV9roz6QoLw== inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -12430,17 +12467,12 @@ inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, i inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA== inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== ini@2.0.0: version "2.0.0" @@ -12491,9 +12523,9 @@ inquirer@^6.2.0: through "^2.3.6" inquirer@^8.2.0: - version "8.2.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.2.tgz#1310517a87a0814d25336c78a20b44c3d9b7629d" - integrity sha512-pG7I/si6K/0X7p1qU+rfWnpTE1UIkTONN1wxtzh0d+dHXtT/JG6qBgLxoyHVsQa8cFABxAPh0pD6uUUHiAoaow== + version "8.2.4" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" + integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== dependencies: ansi-escapes "^4.2.1" chalk "^4.1.1" @@ -12509,14 +12541,7 @@ inquirer@^8.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" - -internal-ip@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" - integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== - dependencies: - default-gateway "^4.2.0" - ipaddr.js "^1.9.0" + wrap-ansi "^7.0.0" internal-slot@^1.0.3: version "1.0.3" @@ -12527,6 +12552,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" @@ -12547,19 +12577,24 @@ invariant@^2.2.3, invariant@^2.2.4: iota-array@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/iota-array/-/iota-array-1.0.0.tgz#81ef57fe5d05814cd58c2483632a99c30a0e8087" - integrity sha1-ge9X/l0FgUzVjCSDYyqZwwoOgIc= - -ip-regex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + integrity sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA== -ip@1.1.5, ip@^1.1.0, ip@^1.1.5: +ip@1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + integrity sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA== -ipaddr.js@1.9.1, ipaddr.js@^1.9.0: +ip@^1.1.0: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" + integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== + +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + +ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== @@ -12572,9 +12607,9 @@ ipaddr.js@^2.0.1: is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= + integrity sha512-vOx7VprsKyllwjSkLV79NIhpyLfr3jAp7VaTCMXOJHu4m0Ew1CZ2fcjASwmV1jI3BWuWHB013M48eyeldk9gYg== -is-absolute-url@^3.0.0, is-absolute-url@^3.0.3: +is-absolute-url@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== @@ -12582,7 +12617,7 @@ is-absolute-url@^3.0.0, is-absolute-url@^3.0.3: is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== dependencies: kind-of "^3.0.2" @@ -12617,7 +12652,7 @@ is-arguments@^1.0.4, is-arguments@^1.1.0: is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== is-arrayish@^0.3.1: version "0.3.2" @@ -12634,7 +12669,7 @@ is-bigint@^1.0.1: is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + integrity sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q== dependencies: binary-extensions "^1.0.0" @@ -12675,10 +12710,17 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-ci@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + is-color-stop@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + integrity sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA== dependencies: css-color-names "^0.0.4" hex-color-regex "^1.1.0" @@ -12687,17 +12729,17 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== +is-core-module@^2.1.0, is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== dependencies: has "^1.0.3" is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== dependencies: kind-of "^3.0.2" @@ -12741,7 +12783,7 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw== is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" @@ -12759,7 +12801,7 @@ is-dom@^1.0.0: is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== is-extendable@^1.0.1: version "1.0.1" @@ -12771,7 +12813,7 @@ is-extendable@^1.0.1: is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-finite@^1.0.0: version "1.1.0" @@ -12781,21 +12823,21 @@ is-finite@^1.0.0: is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-function@^1.0.1, is-function@^1.0.2: +is-function@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== @@ -12815,7 +12857,7 @@ is-generator-function@^1.0.7: is-glob@^3.0.0, is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== dependencies: is-extglob "^2.1.0" @@ -12831,15 +12873,7 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== -is-installed-globally@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - -is-installed-globally@^0.4.0: +is-installed-globally@^0.4.0, is-installed-globally@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== @@ -12860,7 +12894,7 @@ is-map@^2.0.2: is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" - integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== is-negative-zero@^2.0.2: version "2.0.2" @@ -12882,7 +12916,7 @@ is-number-object@^1.0.4: is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== dependencies: kind-of "^3.0.2" @@ -12894,7 +12928,7 @@ is-number@^7.0.0: is-obj@^1.0.0, is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== is-obj@^2.0.0: version "2.0.0" @@ -12940,7 +12974,7 @@ is-path-inside@^3.0.1, is-path-inside@^3.0.2: is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== is-plain-obj@^2.0.0: version "2.1.0" @@ -12980,14 +13014,14 @@ is-regex@^1.0.4, is-regex@^1.1.0, is-regex@^1.1.2, is-regex@^1.1.4: is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-root@2.1.0, is-root@^2.1.0: +is-root@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== @@ -12997,7 +13031,7 @@ is-set@^2.0.2: resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== -is-shared-array-buffer@^1.0.1: +is-shared-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== @@ -13014,7 +13048,7 @@ is-ssh@^1.3.0: is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== is-stream@^2.0.0: version "2.0.1" @@ -13043,7 +13077,7 @@ is-symbol@^1.0.1, is-symbol@^1.0.2, is-symbol@^1.0.3: is-text-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" - integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= + integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w== dependencies: text-extensions "^1.0.0" @@ -13052,21 +13086,21 @@ is-touch-device@^1.0.1: resolved "https://registry.yarnpkg.com/is-touch-device/-/is-touch-device-1.0.1.tgz#9a2fd59f689e9a9bf6ae9a86924c4ba805a42eab" integrity sha512-LAYzo9kMT1b2p19L/1ATGt2XcSilnzNlyvq6c0pbPRVisLbAPpLqr53tIJS00kvrTkj0HtR8U7+u8X0yR8lPSw== -is-typed-array@^1.1.3, is-typed-array@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" - integrity sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA== +is-typed-array@^1.1.3, is-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.9.tgz#246d77d2871e7d9f5aeb1d54b9f52c71329ece67" + integrity sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A== dependencies: available-typed-arrays "^1.0.5" call-bind "^1.0.2" - es-abstract "^1.18.5" - foreach "^2.0.5" + es-abstract "^1.20.0" + for-each "^0.3.3" has-tostringtag "^1.0.0" is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== is-unicode-supported@^0.1.0: version "0.1.0" @@ -13076,7 +13110,7 @@ is-unicode-supported@^0.1.0: is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== is-weakref@^1.0.2: version "1.0.2" @@ -13093,7 +13127,7 @@ is-whitespace-character@^1.0.0: is-window@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" - integrity sha1-LIlspT25feRdPDMTOmXYyfVjSA0= + integrity sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg== is-windows@^1.0.0, is-windows@^1.0.2: version "1.0.2" @@ -13108,7 +13142,7 @@ is-word-character@^1.0.0: is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== is-wsl@^2.1.1, is-wsl@^2.2.0: version "2.2.0" @@ -13125,12 +13159,12 @@ is-yarn-global@^0.3.0: isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== isarray@^2.0.5: version "2.0.5" @@ -13140,19 +13174,19 @@ isarray@^2.0.5: isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== dependencies: isarray "1.0.0" isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== isobject@^4.0.0: version "4.0.0" @@ -13162,19 +13196,27 @@ isobject@^4.0.0: isomorphic-base64@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/isomorphic-base64/-/isomorphic-base64-1.0.2.tgz#f426aae82569ba8a4ec5ca73ad21a44ab1ee7803" - integrity sha1-9Caq6CVpuopOxcpzrSGkSrHueAM= + integrity sha512-pQFyLwShVPA1Qr0dE1ZPguJkbOsFGDfSq6Wzz6XaO33v74X6/iQjgYPozwkeKGQxOI1/H3Fz7+ROtnV1veyKEg== + +isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== -istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.1, istanbul-lib-coverage@^3.2.0: +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== @@ -13193,9 +13235,9 @@ istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: semver "^6.0.0" istanbul-lib-instrument@^5.0.4: - version "5.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" - integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -13239,7 +13281,7 @@ istanbul-reports@^2.2.6: dependencies: html-escaper "^2.0.0" -istanbul-reports@^3.0.2: +istanbul-reports@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== @@ -13260,20 +13302,20 @@ iterate-value@^1.0.2: es-get-iterator "^1.0.2" iterate-iterator "^1.0.1" -jake@^10.6.1: - version "10.8.4" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.4.tgz#f6a8b7bf90c6306f768aa82bb7b98bf4ca15e84a" - integrity sha512-MtWeTkl1qGsWUtbl/Jsca/8xSoK3x0UmS82sNbjqxxG/de/M/3b1DntdjHgPMC50enlTNwXOCRqPXLLt5cCfZA== +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== dependencies: - async "0.9.x" + async "^3.2.3" chalk "^4.0.2" filelist "^1.0.1" minimatch "^3.0.4" jest-canvas-mock@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.3.1.tgz#9535d14bc18ccf1493be36ac37dd349928387826" - integrity sha512-5FnSZPrX3Q2ZfsbYNE3wqKR3+XorN8qFzDzB5o0golWgt6EOX1+emBnpOc9IAQ+NXFj8Nzm3h7ZdE/9H0ylBcg== + version "2.4.0" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.4.0.tgz#947b71442d7719f8e055decaecdb334809465341" + integrity sha512-mmMpZzpmLzn5vepIaHk5HoH3Ka4WykbSoLuG/EKoJd0x0ID/t+INo1l8ByfcUJuDM+RIsL4QDg/gDnBbrj2/IQ== dependencies: cssfontparser "^1.2.1" moo-color "^1.0.2" @@ -13339,6 +13381,16 @@ jest-diff@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-docblock@^24.3.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" @@ -13385,6 +13437,11 @@ jest-get-type@^24.9.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-haste-map@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" @@ -13475,6 +13532,16 @@ jest-matcher-utils@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-matcher-utils@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-message-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" @@ -13689,7 +13756,7 @@ jest-worker@^26.2.1, jest-worker@^26.5.0, jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^27.0.2, jest-worker@^27.4.5: +jest-worker@^27.0.2, jest-worker@^27.3.1, jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== @@ -13706,7 +13773,12 @@ jest@^24.8.0: import-local "^2.0.0" jest-cli "^24.9.0" -joi@^17.3.0, joi@^17.4.0: +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== + +joi@^17.4.0, joi@^17.6.0: version "17.6.0" resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== @@ -13717,21 +13789,24 @@ joi@^17.3.0, joi@^17.4.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -jpeg-js@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" - integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== - js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" - integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= + integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -13740,7 +13815,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.0.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -13750,7 +13825,7 @@ js-yaml@^4.0.0: js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" - integrity sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A= + integrity sha512-eIlkGty7HGmntbV6P/ZlAsoncFLGsNoM27lkTzS+oneY/EiNhj+geqD9ezg/ip+SW6Var0BJU2JtV0vEUZpWVQ== dependencies: argparse "^1.0.7" esprima "^2.6.0" @@ -13758,7 +13833,7 @@ js-yaml@~3.7.0: jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== jsdom@^11.5.1: version "11.12.0" @@ -13800,19 +13875,19 @@ jsesc@^2.5.1: jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-parse-even-better-errors@^2.3.0: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== @@ -13835,24 +13910,17 @@ json-schema@0.4.0, json-schema@^0.4.0: json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json5@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" - integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== - dependencies: - minimist "^1.2.0" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + integrity sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw== json5@^1.0.1: version "1.0.1" @@ -13866,17 +13934,10 @@ json5@^2.1.2, json5@^2.1.3, json5@^2.2.0, json5@^2.2.1: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" @@ -13892,7 +13953,7 @@ jsonfile@^6.0.1: jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== jsonpointer@^5.0.0: version "5.0.0" @@ -13919,19 +13980,24 @@ jsprim@^2.0.2: json-schema "0.4.0" verror "1.10.0" -jssha@^2.1.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/jssha/-/jssha-2.4.2.tgz#d950b095634928bd6b2bda1d42da9a3a762d65e9" - integrity sha512-/jsi/9C0S70zfkT/4UlKQa5E1xKurDnXcQizcww9JSR/Fv+uIbWM2btG+bFcL3iNoK9jIGS0ls9HWLr1iw0kFg== - "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz#6ab1e52c71dfc0c0707008a91729a9491fe9f76c" - integrity sha512-HDAyJ4MNQBboGpUnHAVUNJs6X0lh058s6FuixsFGP7MgJYpD6Vasd6nzSG5iIfXu1zAYlHJ/zsOKNlrenTUBnw== + version "3.3.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz#e624f259143b9062c92b6413ff92a164c80d3ccb" + integrity sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q== dependencies: array-includes "^3.1.4" object.assign "^4.1.2" +jszip@3.9.1: + version "3.9.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.9.1.tgz#784e87f328450d1e8151003a9c67733e2b901051" + integrity sha512-H9A60xPqJ1CuC4Ka6qxzXZeU8aNmgOeP5IFqwJbQQwtu2EUYxota3LdsiZWplF7Wgd9tkAd0mdu36nceSaPuYw== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + junk@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" @@ -13944,22 +14010,17 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" -killable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" - integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== - kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== dependencies: is-buffer "^1.1.5" kind-of@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== dependencies: is-buffer "^1.1.5" @@ -13973,13 +14034,6 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= - optionalDependencies: - graceful-fs "^4.1.9" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -13990,11 +14044,6 @@ klona@^2.0.4, klona@^2.0.5: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - language-subtag-registry@~0.3.2: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" @@ -14003,7 +14052,7 @@ language-subtag-registry@~0.3.2: language-tags@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" - integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== dependencies: language-subtag-registry "~0.3.2" @@ -14025,7 +14074,7 @@ latest-version@^5.1.0: lazy-ass@1.6.0, lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" - integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= + integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== lazy-universal-dotenv@^3.0.1: version "3.0.1" @@ -14083,11 +14132,18 @@ levn@^0.4.1: levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== dependencies: prelude-ls "~1.1.2" type-check "~0.3.2" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lilconfig@^2.0.3, lilconfig@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" @@ -14121,7 +14177,7 @@ lint-staged@^9.0.2: listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" - integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= + integrity sha512-L26cIFm7/oZeSNVhWB6faeorXhMg4HNlb/dS/7jHhr708jxlXrtrBWo4YUxZQkc6dGoxEAe6J/D3juTRBUzjtA== listr-update-renderer@^0.5.0: version "0.5.0" @@ -14147,6 +14203,20 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" +listr2@^3.8.3: + version "3.14.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" + integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.1" + through "^2.3.8" + wrap-ansi "^7.0.0" + listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" @@ -14162,24 +14232,10 @@ listr@^0.14.3: p-map "^2.0.0" rxjs "^6.3.3" -load-bmfont@^1.3.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.1.tgz#c0f5f4711a1e2ccff725a7b6078087ccfcddd3e9" - integrity sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA== - dependencies: - buffer-equal "0.0.1" - mime "^1.3.4" - parse-bmfont-ascii "^1.0.3" - parse-bmfont-binary "^1.0.5" - parse-bmfont-xml "^1.1.4" - phin "^2.9.1" - xhr "^2.0.1" - xtend "^4.0.0" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== dependencies: graceful-fs "^4.1.2" parse-json "^2.2.0" @@ -14190,7 +14246,7 @@ load-json-file@^1.0.0: load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== dependencies: graceful-fs "^4.1.2" parse-json "^4.0.0" @@ -14214,18 +14270,9 @@ loader-runner@^2.4.0: integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== - -loader-utils@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.0" @@ -14245,10 +14292,15 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +loader-utils@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== dependencies: p-locate "^2.0.0" path-exists "^3.0.0" @@ -14287,95 +14339,90 @@ locize-lastused@^1.1.0: resolved "https://registry.yarnpkg.com/locize-lastused/-/locize-lastused-1.1.1.tgz#ca83a7386dcd74ee0cae07c8ef579cb24f345b9b" integrity sha512-zq310En1BWRRjWaYdTScUwCWUcvLJuOUqFpN5LBQoCccWg9CQB5gDg/D+b8YjR2GkofRGy08C+4LGpqcGqfkHA== -lodash-es@^4.17.15: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== - lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= + integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA== lodash.assignin@^4.0.9: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= + integrity sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg== lodash.bind@^4.1.4: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" - integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= + integrity sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA== -lodash.clonedeep@^4.5.0: +lodash.clonedeep@4.5.0, lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== lodash.curry@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" - integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA= + integrity sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA== lodash.debounce@4.0.8, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== lodash.defaults@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== lodash.filter@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= + integrity sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ== lodash.flatten@^4.2.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== lodash.flow@^3.3.0: version "3.5.0" resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" - integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= + integrity sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw== lodash.foreach@^4.3.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" - integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= + integrity sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ== lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== lodash.isequal@4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" - integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= + integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g== lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== lodash.map@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= + integrity sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q== lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.4.0, lodash.merge@^4.6.1, lodash.merge@^4.6.2: version "4.6.2" @@ -14385,39 +14432,39 @@ lodash.merge@^4.4.0, lodash.merge@^4.6.1, lodash.merge@^4.6.2: lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== lodash.pick@^4.2.1: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== lodash.reduce@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= + integrity sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw== lodash.reject@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" - integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= + integrity sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ== lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" - integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= + integrity sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg== lodash.some@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" - integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= + integrity sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ== lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== -lodash.template@^4.0.2, lodash.template@^4.4.0, lodash.template@^4.5.0: +lodash.template@^4.0.2, lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== @@ -14435,24 +14482,24 @@ lodash.templatesettings@^4.0.0: lodash.topath@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" - integrity sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak= + integrity sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg== lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== lodash@4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.1.1, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.2.1: +lodash@4.17.21, lodash@^4.1.1, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.2.1: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -14460,7 +14507,7 @@ lodash@^4.1.1, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.1 log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" - integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg= + integrity sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ== dependencies: chalk "^1.0.0" @@ -14482,27 +14529,21 @@ log-symbols@^4.0.0, log-symbols@^4.1.0: log-update@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" - integrity sha1-iDKP19HOeTiykoN0bwsbwSayRwg= + integrity sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg== dependencies: ansi-escapes "^3.0.0" cli-cursor "^2.0.0" wrap-ansi "^3.0.1" -logform@^2.3.2, logform@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.0.tgz#131651715a17d50f09c2a2c1a524ff1a4164bcfe" - integrity sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw== +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== dependencies: - "@colors/colors" "1.5.0" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - -loglevel@^1.6.8: - version "1.8.0" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" - integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" loglevelnext@^3.0.1: version "3.0.1" @@ -14519,7 +14560,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + integrity sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ== dependencies: currently-unhandled "^0.4.1" signal-exit "^3.0.0" @@ -14541,7 +14582,7 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lowlight@^1.14.0: +lowlight@^1.17.0: version "1.20.0" resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw== @@ -14571,11 +14612,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^7.4.0: - version "7.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.7.3.tgz#98cd19eef89ce6a4a3c4502c17c833888677c252" - integrity sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw== - macos-release@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" @@ -14644,17 +14680,17 @@ map-age-cleaner@^0.1.3: map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== map-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" - integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= + integrity sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ== map-obj@^4.0.0: version "4.3.0" @@ -14664,17 +14700,17 @@ map-obj@^4.0.0: map-or-similar@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" - integrity sha1-beJlMXSt+12e3DPGnT6Sobdvrwg= + integrity sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg== map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" - integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= + integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g== map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== dependencies: object-visit "^1.0.0" @@ -14683,11 +14719,6 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== -markdown-to-jsx@^7.1.3: - version "7.1.7" - resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz#a5f22102fb12241c8cea1ca6a4050bb76b23a25d" - integrity sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w== - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -14748,12 +14779,12 @@ mdn-data@2.0.4: mdurl@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== medium-zoom@^1.0.4: version "1.0.6" @@ -14768,10 +14799,10 @@ mem@^8.1.1: map-age-cleaner "^0.1.3" mimic-fn "^3.1.0" -memfs@^3.1.2, memfs@^3.2.2, memfs@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" - integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== +memfs@^3.1.2, memfs@^3.2.2, memfs@^3.4.3: + version "3.4.4" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.4.tgz#e8973cd8060548916adcca58a248e7805c715e89" + integrity sha512-W4gHNUE++1oSJVn8Y68jPXi+mkx3fXR5ITE/Ubz6EQ3xRpCN5k2CQ4AUR8094Z7211F876TyoBACGsIveqgiGA== dependencies: fs-monkey "1.0.3" @@ -14783,14 +14814,14 @@ memoize-one@^5.0.0: memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" - integrity sha1-fIekZGREwy11Q4VwkF8tvRsagFo= + integrity sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog== dependencies: map-or-similar "^1.5.0" memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + integrity sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ== dependencies: errno "^0.1.3" readable-stream "^2.0.1" @@ -14803,10 +14834,10 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.3.0: +meow@^3.1.0, meow@^3.3.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + integrity sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA== dependencies: camelcase-keys "^2.0.0" decamelize "^1.1.2" @@ -14854,7 +14885,7 @@ meow@^8.0.0: merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-stream@^2.0.0: version "2.0.0" @@ -14869,7 +14900,7 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== microevent.ts@~0.1.1: version "0.1.1" @@ -14895,7 +14926,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -14935,7 +14966,7 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.30, mime-types@^2.1.31, dependencies: mime-db "1.52.0" -mime@1.6.0, mime@^1.3.4: +mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -14970,15 +15001,15 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= + integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== dependencies: dom-walk "^0.1.0" @@ -14995,16 +15026,7 @@ mini-create-react-context@^0.4.0: "@babel/runtime" "^7.12.1" tiny-warning "^1.0.3" -mini-css-extract-plugin@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" - integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" - -mini-css-extract-plugin@^2.1.0: +mini-css-extract-plugin@^2.1.0, mini-css-extract-plugin@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w== @@ -15019,7 +15041,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== minimatch@3.0.4: version "3.0.4" @@ -15028,13 +15050,20 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.2: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -15140,7 +15169,7 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: mkdirp-promise@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" - integrity sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE= + integrity sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w== dependencies: mkdirp "*" @@ -15149,7 +15178,7 @@ mkdirp@*, mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -15166,20 +15195,15 @@ modify-values@^1.0.0: resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -module-alias@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0" - integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q== - moment@2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== -moment@>=1.6.0, moment@^2.24.0, moment@^2.29.1: - version "2.29.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" - integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== +moment@>=1.6.0, moment@^2.24.0: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== moo-color@^1.0.2: version "1.0.3" @@ -15196,7 +15220,7 @@ mousetrap@^1.6.5: move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + integrity sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ== dependencies: aproba "^1.1.1" copy-concurrently "^1.0.0" @@ -15213,7 +15237,7 @@ mrmime@^1.0.0: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.1: version "2.1.1" @@ -15233,7 +15257,7 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + integrity sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ== multicast-dns@^6.0.1: version "6.2.3" @@ -15243,6 +15267,14 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +multicast-dns@^7.2.4: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + multimatch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-3.0.0.tgz#0e2534cc6bc238d9ab67e1b9cd5fcd85a6dbf70b" @@ -15261,7 +15293,7 @@ mustache@^4.2.0: mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ== mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" @@ -15278,14 +15310,14 @@ mz@^2.5.0: thenify-all "^1.0.0" nan@^2.12.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" - integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== + version "2.16.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" + integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== -nanoid@^3.1.23, nanoid@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" - integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== +nanoid@^3.3.1, nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== nanomatch@^1.2.9: version "1.2.13" @@ -15312,17 +15344,12 @@ napi-build-utils@^1.0.1: natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -natural-orderby@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/natural-orderby/-/natural-orderby-2.0.3.tgz#8623bc518ba162f8ff1cdb8941d74deb0fdcc016" - integrity sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q== + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== ncp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" - integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= + integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA== ndarray@^1.0.19: version "1.0.19" @@ -15360,22 +15387,22 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-abi@^2.21.0: - version "2.30.1" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.1.tgz#c437d4b1fe0e285aaf290d45b45d4d7afedac4cf" - integrity sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w== +node-abi@^3.3.0: + version "3.22.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.22.0.tgz#00b8250e86a0816576258227edbce7bbe0039362" + integrity sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w== dependencies: - semver "^5.4.1" + semver "^7.3.5" -node-addon-api@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" - integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== +node-addon-api@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" + integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== node-dir@^0.1.10: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" - integrity sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU= + integrity sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg== dependencies: minimatch "^3.0.2" @@ -15408,19 +15435,14 @@ node-fetch@2.6.7, node-fetch@^2.5.0, node-fetch@^2.6.1, node-fetch@^2.6.7: whatwg-url "^5.0.0" node-fetch@^3.1.1: - version "3.2.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.3.tgz#a03c9cc2044d21d1a021566bd52f080f333719a6" - integrity sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA== + version "3.2.4" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.4.tgz#3fbca2d8838111048232de54cb532bd3cf134947" + integrity sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw== dependencies: data-uri-to-buffer "^4.0.0" fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== - node-forge@^1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -15446,7 +15468,7 @@ node-gyp@^5.0.2: node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-libs-browser@^2.2.1: version "2.2.1" @@ -15488,34 +15510,16 @@ node-notifier@^5.4.2: shellwords "^0.1.1" which "^1.3.0" -node-releases@^1.1.61: - version "1.1.77" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e" - integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== - -node-releases@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" - integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== +node-releases@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== node-version@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/node-version/-/node-version-1.2.0.tgz#34fde3ffa8e1149bd323983479dda620e1b5060d" integrity sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ== -node-vibrant@^3.1.5: - version "3.1.6" - resolved "https://registry.yarnpkg.com/node-vibrant/-/node-vibrant-3.1.6.tgz#8554c3108903232cbe1e722f928469ee4379aa18" - integrity sha512-Wlc/hQmBMOu6xon12ZJHS2N3M+I6J8DhrD3Yo6m5175v8sFkVIN+UjhKVRcO+fqvre89ASTpmiFEP3nPO13SwA== - dependencies: - "@jimp/custom" "^0.16.1" - "@jimp/plugin-resize" "^0.16.1" - "@jimp/types" "^0.16.1" - "@types/lodash" "^4.14.53" - "@types/node" "^10.11.7" - lodash "^4.17.20" - url "^0.11.0" - nopt@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" @@ -15547,7 +15551,7 @@ normalize-package-data@^3.0.0: normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== dependencies: remove-trailing-separator "^1.0.1" @@ -15559,7 +15563,17 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ== + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" normalize-url@^3.0.0: version "3.3.0" @@ -15633,7 +15647,7 @@ npm-pick-manifest@^3.0.0: npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== dependencies: path-key "^2.0.0" @@ -15686,7 +15700,7 @@ npmlog@^5.0.1: nprogress@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" - integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E= + integrity sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA== nth-check@^1.0.2, nth-check@~1.0.1: version "1.0.2" @@ -15696,21 +15710,21 @@ nth-check@^1.0.2, nth-check@~1.0.1: boolbase "~1.0.0" nth-check@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" - integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== dependencies: boolbase "^1.0.0" num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= + integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg== number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== nwsapi@^2.0.7: version "2.2.0" @@ -15722,15 +15736,15 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== dependencies: copy-descriptor "^0.1.0" define-property "^0.2.5" @@ -15747,9 +15761,9 @@ object-hash@^2.2.0: integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== object-inspect@^1.12.0, object-inspect@^1.9.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" - integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== object-is@^1.0.1, object-is@^1.1.2: version "1.1.5" @@ -15759,20 +15773,15 @@ object-is@^1.0.1, object-is@^1.1.2: call-bind "^1.0.2" define-properties "^1.1.3" -object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-treeify@^1.1.4: - version "1.1.33" - resolved "https://registry.yarnpkg.com/object-treeify/-/object-treeify-1.1.33.tgz#f06fece986830a3cba78ddd32d4c11d1f76cdf40" - integrity sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A== - object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== dependencies: isobject "^3.0.0" @@ -15805,26 +15814,27 @@ object.entries@^1.1.0, object.entries@^1.1.2, object.entries@^1.1.5: es-abstract "^1.19.1" object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0, object.getownpropertydescriptors@^2.1.1, object.getownpropertydescriptors@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" - integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== + version "2.1.4" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" + integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== dependencies: + array.prototype.reduce "^1.0.4" call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.20.1" -object.hasown@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5" - integrity sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg== +object.hasown@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.1.tgz#ad1eecc60d03f49460600430d97f23882cf592a3" + integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A== dependencies: - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.19.5" object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== dependencies: isobject "^3.0.1" @@ -15863,11 +15873,6 @@ oidc-client@1.11.5: crypto-js "^4.0.0" serialize-javascript "^4.0.0" -omggif@^1.0.10, omggif@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" - integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== - on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -15875,13 +15880,6 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - on-headers@~1.0.1, on-headers@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" @@ -15890,26 +15888,14 @@ on-headers@~1.0.1, on-headers@~1.0.2: once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== dependencies: mimic-fn "^1.0.0" @@ -15927,7 +15913,7 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" -open@^7.0.2, open@^7.0.3: +open@^7.0.3: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== @@ -15935,7 +15921,7 @@ open@^7.0.2, open@^7.0.3: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^8.0.9: +open@^8.0.9, open@^8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== @@ -15954,13 +15940,6 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== -opn@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" - integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== - dependencies: - is-wsl "^1.1.0" - optimize-css-assets-webpack-plugin@^5.0.3: version "5.0.8" resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.8.tgz#cbccdcf5a6ef61d4f8cc78cf083a67446e5f402a" @@ -16008,22 +15987,15 @@ ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" - os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== os-name@^3.1.0: version "3.1.0" @@ -16036,7 +16008,7 @@ os-name@^3.1.0: os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== osenv@^0.1.4, osenv@^0.1.5: version "0.1.5" @@ -16049,12 +16021,7 @@ osenv@^0.1.4, osenv@^0.1.5: ospath@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" - integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= - -overlayscrollbars@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz#0b840a88737f43a946b9d87875a2f9e421d0338a" - integrity sha512-gIQfzgGgu1wy80EB4/6DaJGHMEGmizq27xHIESrzXq0Y/J0Ay1P3DWk6tuVmEPIZH15zaBlxeEJOqdJKmowHCQ== + integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== p-all@^2.1.0: version "2.1.0" @@ -16071,12 +16038,12 @@ p-cancelable@^1.0.0: p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== p-each-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" - integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + integrity sha512-J/e9xiZZQNrt+958FFzJ+auItsBGq+UrQ7nE89AUP7UOTtjHnkISANXLdayhVzh538UnLMCSlf13lFfRIAKQOA== dependencies: p-reduce "^1.0.0" @@ -16097,7 +16064,7 @@ p-filter@^2.1.0: p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== p-finally@^2.0.0: version "2.0.1" @@ -16118,7 +16085,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -16128,7 +16095,7 @@ p-limit@^3.0.2, p-limit@^3.1.0: p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== dependencies: p-limit "^1.1.0" @@ -16156,7 +16123,7 @@ p-locate@^5.0.0: p-map-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" - integrity sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco= + integrity sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg== dependencies: p-reduce "^1.0.0" @@ -16182,7 +16149,7 @@ p-map@^4.0.0: p-pipe@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" - integrity sha1-SxoROZoRUgpneQ7loMHViB1r7+k= + integrity sha512-IA8SqjIGA8l9qOksXJvsvkeQ+VGb0TAzNCzvKvz9wt5wWLqfWbV6fXy43gpR2L4Te8sOq3S+Ql9biAaMKPdbtw== p-queue@^4.0.0: version "4.0.0" @@ -16194,21 +16161,14 @@ p-queue@^4.0.0: p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" - integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= - -p-retry@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" - integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== - dependencies: - retry "^0.12.0" + integrity sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ== p-retry@^4.5.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.1.tgz#8fcddd5cdf7a67a0911a9cf2ef0e5df7f602316c" - integrity sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA== + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== dependencies: - "@types/retry" "^0.12.0" + "@types/retry" "0.12.0" retry "^0.13.1" p-timeout@^3.1.0: @@ -16221,7 +16181,7 @@ p-timeout@^3.1.0: p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== p-try@^2.0.0: version "2.2.0" @@ -16231,7 +16191,7 @@ p-try@^2.0.0: p-waterfall@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-1.0.0.tgz#7ed94b3ceb3332782353af6aae11aa9fc235bb00" - integrity sha1-ftlLPOszMngjU69qrhGqn8I1uwA= + integrity sha512-KeXddIp6jBT8qzyxfQGOGzNYc/7ftxKtRc5Uggre02yvbZrSBHE2M2C842/WizMBFD4s0Ngwz3QFOit2A+Ezrg== dependencies: p-reduce "^1.0.0" @@ -16245,16 +16205,16 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -pako@^1.0.5, pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -pako@^2.0.4: +pako@2.0.4, pako@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== +pako@~1.0.2, pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parallel-transform@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" @@ -16290,24 +16250,6 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-bmfont-ascii@^1.0.3: - version "1.0.6" - resolved "https://registry.yarnpkg.com/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz#11ac3c3ff58f7c2020ab22769079108d4dfa0285" - integrity sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU= - -parse-bmfont-binary@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz#d038b476d3e9dd9db1e11a0b0e53a22792b69006" - integrity sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY= - -parse-bmfont-xml@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz#015319797e3e12f9e739c4d513872cd2fa35f389" - integrity sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ== - dependencies: - xml-parse-from-string "^1.0.0" - xml2js "^0.4.5" - parse-entities@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" @@ -16323,24 +16265,19 @@ parse-entities@^2.0.0: parse-github-repo-url@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" - integrity sha1-nn2LslKmy2ukJZUGC3v23z28H1A= - -parse-headers@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" - integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== + integrity sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg== parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== dependencies: error-ex "^1.2.0" parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" @@ -16355,7 +16292,7 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-numeric-range@^1.2.0: +parse-numeric-range@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== @@ -16380,6 +16317,14 @@ parse-url@^6.0.0: parse-path "^4.0.0" protocols "^1.4.0" +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + parse5@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" @@ -16395,6 +16340,13 @@ parse5@^6.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.0.0.tgz#51f74a5257f5fcc536389e8c2d0b3802e1bfa91a" + integrity sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g== + dependencies: + entities "^4.3.0" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -16411,15 +16363,7 @@ pascal-case@^3.1.2: pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -password-prompt@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923" - integrity sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA== - dependencies: - ansi-escapes "^3.1.0" - cross-spawn "^6.0.5" + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== path-browserify@0.0.1: version "0.0.1" @@ -16434,19 +16378,19 @@ path-browserify@^1.0.1: path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== dependencies: pinkie-promise "^2.0.0" path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== path-exists@^4.0.0: version "4.0.0" @@ -16456,17 +16400,17 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-is-inside@1.0.2, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" @@ -16486,7 +16430,7 @@ path-parse@^1.0.6, path-parse@^1.0.7: path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-to-regexp@2.2.1: version "2.2.1" @@ -16503,7 +16447,7 @@ path-to-regexp@^1.7.0: path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== dependencies: graceful-fs "^4.1.2" pify "^2.0.0" @@ -16524,7 +16468,7 @@ path-type@^4.0.0: pause-stream@0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= + integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A== dependencies: through "~2.3" @@ -16544,31 +16488,11 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= -percy-client@^3.2.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/percy-client/-/percy-client-3.8.0.tgz#e838570d8815fa033edaf917e5efbd7a5a1bba57" - integrity sha512-6SVEpnPteN9mR4fq/FCW7M0KDHWbNAyiiyj9igTpHSv2oBjgyNnDA2y0S+o8U+AN7QDRbh40JbAWi72M+cfOJg== - dependencies: - bluebird "^3.5.1" - bluebird-retry "^0.11.0" - dotenv "^8.1.0" - es6-promise-pool "^2.5.0" - jssha "^2.1.0" - regenerator-runtime "^0.13.1" - request "^2.87.0" - request-promise "^4.2.2" - walk "^2.3.14" - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -phin@^2.9.1: - version "2.9.3" - resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c" - integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA== - picocolors@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" @@ -16616,13 +16540,6 @@ pirates@^4.0.1, pirates@^4.0.5: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== -pixelmatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854" - integrity sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ= - dependencies: - pngjs "^3.0.0" - pkg-config@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/pkg-config/-/pkg-config-1.1.1.tgz#557ef22d73da3c8837107766c52eadabde298fe4" @@ -16662,13 +16579,6 @@ pkg-install@^1.0.0: "@types/node" "^11.9.4" execa "^1.0.0" -pkg-up@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" - integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== - dependencies: - find-up "^3.0.0" - pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" @@ -16676,6 +16586,13 @@ pkg-up@^2.0.0: dependencies: find-up "^2.1.0" +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + please-upgrade-node@^3.1.1, please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -16694,11 +16611,6 @@ pn@^1.1.0: resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== -pngjs@^3.0.0, pngjs@^3.3.3: - version "3.4.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" - integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== - pnp-webpack-plugin@1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" @@ -16706,14 +16618,14 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" -polished@^4.0.5: +polished@^4.0.5, polished@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1" integrity sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ== dependencies: "@babel/runtime" "^7.17.8" -portfinder@^1.0.26, portfinder@^1.0.28: +portfinder@^1.0.28: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== @@ -16758,10 +16670,10 @@ postcss-clamp@^4.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-color-functional-notation@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.2.tgz#f59ccaeb4ee78f1b32987d43df146109cc743073" - integrity sha512-DXVtwUhIk4f49KK5EGuEdgx4Gnyj6+t2jBSEmxvpIK9QI40tWrpS2Pua8Q7iIZWBrki2QOaeUdEaLPPa91K0RQ== +postcss-color-functional-notation@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.3.tgz#23c9d73c76113b75473edcf66f443c6f1872bd0f" + integrity sha512-5fbr6FzFzjwHXKsVnkmEYrJYG8VNNzvD1tAXaPPWR97S6rhKI5uh2yOfV5TAzhDkZoq4h+chxEplFDc8GeyFtw== dependencies: postcss-value-parser "^4.2.0" @@ -16808,11 +16720,12 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-convert-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10" - integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g== +postcss-convert-values@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz#31586df4e184c2e8890e8b34a0b9355313f503ab" + integrity sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g== dependencies: + browserslist "^4.20.3" postcss-value-parser "^4.2.0" postcss-custom-media@^8.0.0: @@ -16820,10 +16733,10 @@ postcss-custom-media@^8.0.0: resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz#1be6aff8be7dc9bf1fe014bde3b71b92bb4552f1" integrity sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g== -postcss-custom-properties@^12.1.5: - version "12.1.6" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.6.tgz#345b5b64c9520bb66390393646e8d5fbb7f10b58" - integrity sha512-QEnQkDkb+J+j2bfJisJJpTAFL+lUFl66rUNvnjPBIvRbZACLG4Eu5bmBCIY4FJCqhwsfbBpmJUyb3FcR/31lAg== +postcss-custom-properties@^12.1.7: + version "12.1.7" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.7.tgz#ca470fd4bbac5a87fd868636dafc084bc2a78b41" + integrity sha512-N/hYP5gSoFhaqxi2DPCmvto/ZcRDVjE3T1LiAMzc/bg53hvhcHOLpXOHb526LzBBp5ZlAUhkuot/bfpmpgStJg== dependencies: postcss-value-parser "^4.2.0" @@ -16848,10 +16761,10 @@ postcss-discard-comments@^4.0.2: dependencies: postcss "^7.0.0" -postcss-discard-comments@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369" - integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ== +postcss-discard-comments@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" + integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== postcss-discard-duplicates@^4.0.2: version "4.0.2" @@ -16971,7 +16884,7 @@ postcss-js@^3.0.3: camelcase-css "^2.0.1" postcss "^8.1.6" -postcss-lab-function@^4.1.2: +postcss-lab-function@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.2.0.tgz#e054e662c6480202f5760887ec1ae0d153357123" integrity sha512-Zb1EO9DGYfa3CP8LhINHCcTTCTLI+R3t7AX2mKsDzdgVQ/GkCpHOTgOr6HBHslP7XDdVbqgHW5vvRPMdVANQ8w== @@ -16998,16 +16911,7 @@ postcss-loader@^4.2.0: schema-utils "^3.0.0" semver "^7.3.4" -postcss-loader@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-5.3.0.tgz#1657f869e48d4fdb018a40771c235e499ee26244" - integrity sha512-/+Z1RAmssdiSLgIZwnJHwBMnlABPgF7giYzTN2NOfr9D21IJZ4mQC1R2miwp80zno9M4zMD/umGI8cR+2EL5zw== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.4" - semver "^7.3.4" - -postcss-loader@^6.1.1: +postcss-loader@^6.1.1, postcss-loader@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== @@ -17044,10 +16948,10 @@ postcss-merge-longhand@^4.0.11: postcss-value-parser "^3.0.0" stylehacks "^4.0.0" -postcss-merge-longhand@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.4.tgz#0f46f8753989a33260efc47de9a0cdc571f2ec5c" - integrity sha512-hbqRRqYfmXoGpzYKeW0/NCZhvNyQIlQeWVSao5iKWdyx7skLvCfQFGIUsP9NUs3dSbPac2IC4Go85/zG+7MlmA== +postcss-merge-longhand@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.5.tgz#b0e03bee3b964336f5f33c4fc8eacae608e91c05" + integrity sha512-NOG1grw9wIO+60arKa2YYsrbgvP6tp+jqc7+ZD5/MalIw234ooH2C6KlR6FEn4yle7GqZoBxSK1mLBE9KPur6w== dependencies: postcss-value-parser "^4.2.0" stylehacks "^5.1.0" @@ -17064,10 +16968,10 @@ postcss-merge-rules@^4.0.3: postcss-selector-parser "^3.0.0" vendors "^1.0.0" -postcss-merge-rules@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162" - integrity sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww== +postcss-merge-rules@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz#7049a14d4211045412116d79b751def4484473a5" + integrity sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" @@ -17120,10 +17024,10 @@ postcss-minify-params@^4.0.2: postcss-value-parser "^3.0.0" uniqs "^2.0.0" -postcss-minify-params@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.2.tgz#77e250780c64198289c954884ebe3ee4481c3b1c" - integrity sha512-aEP+p71S/urY48HWaRHasyx4WHQJyOYaKpQ6eXl8k0kxg66Wt/30VR6/woh8THgcpRbonJD5IeD+CzNhPi1L8g== +postcss-minify-params@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz#ac41a6465be2db735099bbd1798d85079a6dc1f9" + integrity sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg== dependencies: browserslist "^4.16.6" cssnano-utils "^3.1.0" @@ -17139,10 +17043,10 @@ postcss-minify-selectors@^4.0.2: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -postcss-minify-selectors@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c" - integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA== +postcss-minify-selectors@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" + integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== dependencies: postcss-selector-parser "^6.0.5" @@ -17214,11 +17118,12 @@ postcss-nested@5.0.5: dependencies: postcss-selector-parser "^6.0.4" -postcss-nesting@^10.1.3: - version "10.1.4" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.4.tgz#80de9d1c2717bc44df918dd7f118929300192a7a" - integrity sha512-2ixdQ59ik/Gt1+oPHiI1kHdwEI8lLKEmui9B1nl6163ANLC+GewQn7fXMxJF2JSb4i2MKL96GU8fIiQztK4TTA== +postcss-nesting@^10.1.7: + version "10.1.7" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.7.tgz#0101bd6c7d386e7ad8e2e86ebcc0e0109833b86e" + integrity sha512-Btho5XzDTpl117SmB3tvUHP8txg5n7Ayv7vQ5m4b1zXkfs1Y52C67uZjZ746h7QvOJ+rLRg50OlhhjFW+IQY6A== dependencies: + "@csstools/selector-specificity" "1.0.0" postcss-selector-parser "^6.0.10" postcss-normalize-charset@^4.0.1: @@ -17405,31 +17310,35 @@ postcss-place@^7.0.4: postcss-value-parser "^4.2.0" postcss-preset-env@^7.4.3: - version "7.4.3" - resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.4.3.tgz#fb1c8b4cb405da042da0ddb8c5eda7842c08a449" - integrity sha512-dlPA65g9KuGv7YsmGyCKtFkZKCPLkoVMUE3omOl6yM+qrynVHxFvf0tMuippIrXB/sB/MyhL1FgTIbrO+qMERg== + version "7.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.7.0.tgz#bcc9be9725a85d34e72a8fa69dc5e1130abee301" + integrity sha512-2Q9YARQju+j2BVgAyDnW1pIWIMlaHZqbaGISPMmalznNlWcNFIZFQsJfRLXS+WHmHJDCmV7wIWpVf9JNKR4Elw== dependencies: - "@csstools/postcss-color-function" "^1.0.3" + "@csstools/postcss-cascade-layers" "^1.0.2" + "@csstools/postcss-color-function" "^1.1.0" "@csstools/postcss-font-format-keywords" "^1.0.0" - "@csstools/postcss-hwb-function" "^1.0.0" + "@csstools/postcss-hwb-function" "^1.0.1" "@csstools/postcss-ic-unit" "^1.0.0" - "@csstools/postcss-is-pseudo-class" "^2.0.1" + "@csstools/postcss-is-pseudo-class" "^2.0.4" "@csstools/postcss-normalize-display-values" "^1.0.0" - "@csstools/postcss-oklab-function" "^1.0.2" + "@csstools/postcss-oklab-function" "^1.1.0" "@csstools/postcss-progressive-custom-properties" "^1.3.0" - autoprefixer "^10.4.4" - browserslist "^4.20.2" + "@csstools/postcss-stepped-value-functions" "^1.0.0" + "@csstools/postcss-trigonometric-functions" "^1.0.0" + "@csstools/postcss-unset-value" "^1.0.1" + autoprefixer "^10.4.7" + browserslist "^4.20.3" css-blank-pseudo "^3.0.3" css-has-pseudo "^3.0.4" css-prefers-color-scheme "^6.0.3" - cssdb "^6.5.0" + cssdb "^6.6.2" postcss-attribute-case-insensitive "^5.0.0" postcss-clamp "^4.1.0" - postcss-color-functional-notation "^4.2.2" + postcss-color-functional-notation "^4.2.3" postcss-color-hex-alpha "^8.0.3" postcss-color-rebeccapurple "^7.0.2" postcss-custom-media "^8.0.0" - postcss-custom-properties "^12.1.5" + postcss-custom-properties "^12.1.7" postcss-custom-selectors "^6.0.0" postcss-dir-pseudo-class "^6.0.4" postcss-double-position-gradients "^3.1.1" @@ -17440,23 +17349,23 @@ postcss-preset-env@^7.4.3: postcss-gap-properties "^3.0.3" postcss-image-set-function "^4.0.6" postcss-initial "^4.0.1" - postcss-lab-function "^4.1.2" + postcss-lab-function "^4.2.0" postcss-logical "^5.0.4" postcss-media-minmax "^5.0.0" - postcss-nesting "^10.1.3" + postcss-nesting "^10.1.7" postcss-opacity-percentage "^1.1.2" postcss-overflow-shorthand "^3.0.3" postcss-page-break "^3.0.4" postcss-place "^7.0.4" - postcss-pseudo-class-any-link "^7.1.1" + postcss-pseudo-class-any-link "^7.1.4" postcss-replace-overflow-wrap "^4.0.0" postcss-selector-not "^5.0.0" postcss-value-parser "^4.2.0" -postcss-pseudo-class-any-link@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.2.tgz#81ec491aa43f97f9015e998b7a14263b4630bdf0" - integrity sha512-76XzEQv3g+Vgnz3tmqh3pqQyRojkcJ+pjaePsyhcyf164p9aZsu3t+NWxkZYbcHLK1ju5Qmalti2jPI5IWCe5w== +postcss-pseudo-class-any-link@^7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.4.tgz#ac72aac4fe11fc4a0a368691f8fd5fe89e95aba4" + integrity sha512-JxRcLXm96u14N3RzFavPIE9cRPuOqLDuzKeBsqi4oRk4vt8n0A7I0plFs/VXTg7U2n7g/XkQi0OwqTO3VWBfEg== dependencies: postcss-selector-parser "^6.0.10" @@ -17531,10 +17440,10 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.10, postcss-selecto cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-sort-media-queries@^3.10.11: - version "3.12.13" - resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-3.12.13.tgz#213b1422e7efc29e0c2519b6ab259ff7d50b2156" - integrity sha512-bFbR1+P6HhZWXcT5DVV2pBH5Y2U5daKbFd0j+kcwKdzrxkbmgFu0GhI2JfFUyy5KQIeW+YJGP+vwNDOS5hIn2g== +postcss-sort-media-queries@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.2.1.tgz#a99bae69ef1098ee3b64a5fa94d258ec240d0355" + integrity sha512-9VYekQalFZ3sdgcTjXMa0dDjsfBVHXlraYJEMiOJ/2iMmI2JGCMavP16z3kWOaRu8NSaJCTgVpB/IVpH5yT9YQ== dependencies: sort-css-media-queries "2.0.4" @@ -17594,31 +17503,31 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.2 picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.1.6, postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5: - version "8.4.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" - integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== +postcss@^8.1.6, postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5, postcss@^8.4.13, postcss@^8.4.7: + version "8.4.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" + integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== dependencies: - nanoid "^3.3.1" + nanoid "^3.3.4" picocolors "^1.0.0" source-map-js "^1.0.2" -prebuild-install@^6.1.2: - version "6.1.4" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f" - integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ== +prebuild-install@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.0.tgz#991b6ac16c81591ba40a6d5de93fb33673ac1370" + integrity sha512-CNcMgI1xBypOyGqjp3wOc8AAo1nMhZS3Cwd3iHIxOdAUbb+YxdNuM4Z5iIrZ8RLvOsf3F3bl7b7xGq6DjQoNYA== dependencies: - detect-libc "^1.0.3" + detect-libc "^2.0.0" expand-template "^2.0.3" github-from-package "0.0.0" minimist "^1.2.3" mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" - node-abi "^2.21.0" + node-abi "^3.3.0" npmlog "^4.0.1" pump "^3.0.0" rc "^1.2.7" - simple-get "^3.0.3" + simple-get "^4.0.0" tar-fs "^2.0.0" tunnel-agent "^0.6.0" @@ -17632,6 +17541,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -17654,7 +17568,7 @@ prettier@^1.18.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: +pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== @@ -17685,6 +17599,15 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-format@^27.0.0, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-hrtime@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -17695,12 +17618,17 @@ pretty-time@^1.1.0: resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== -prism-react-renderer@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" - integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== +prism-react-renderer@^1.0.1, prism-react-renderer@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.3.tgz#9b5a4211a6756eee3c96fee9a05733abc0b0805c" + integrity sha512-Viur/7tBTCH2HmYzwCHmt2rEFn+rdIWNIINXyg0StiISbDiIhHKhrFuEK8eMkKgvsIYSjgGqy/hNyucHp6FpoQ== + +prismjs@^1.27.0, prismjs@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6" + integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw== -prismjs@^1.21.0, prismjs@^1.23.0, prismjs@~1.27.0: +prismjs@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== @@ -17715,7 +17643,7 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^2.0.0, progress@^2.0.1: +progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -17766,15 +17694,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prompts@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" - integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.1: +prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -17807,7 +17727,7 @@ prop-types@15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -17848,10 +17768,10 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-from-env@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +proxy-from-env@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= prr@~1.0.1: version "1.0.1" @@ -17934,24 +17854,6 @@ pupa@^2.1.1: dependencies: escape-goat "^2.0.0" -puppeteer@^5.3.1: - version "5.5.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.5.0.tgz#331a7edd212ca06b4a556156435f58cbae08af00" - integrity sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg== - dependencies: - debug "^4.1.0" - devtools-protocol "0.0.818844" - extract-zip "^2.0.0" - https-proxy-agent "^4.0.0" - node-fetch "^2.6.1" - pkg-dir "^4.2.0" - progress "^2.0.1" - proxy-from-env "^1.0.0" - rimraf "^3.0.2" - tar-fs "^2.0.0" - unbzip2-stream "^1.3.3" - ws "^7.2.3" - pure-color@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" @@ -17979,16 +17881,19 @@ qs@6.10.3, qs@^6.10.0, qs@^6.9.4: dependencies: side-channel "^1.0.4" -qs@6.9.7: - version "6.9.7" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" - integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== - qs@~6.5.2: version "6.5.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + query-string@^6.12.1, query-string@^6.13.8, query-string@^6.14.0: version "6.14.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" @@ -18014,20 +17919,15 @@ querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -queue@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.1.tgz#abd5a5b0376912f070a25729e0b6a7d565683791" - integrity sha512-AJBQabRCCNr9ANq8v77RJEv73DPbn55cdTb+Giq4X0AVnNVZvMHlYp7XlQiN+1npCZj1DuSmaA2hYVUUDgxFDg== +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== dependencies: inherits "~2.0.3" @@ -18046,11 +17946,6 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== -raf-schd@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" - integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== - raf@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" @@ -18058,15 +17953,10 @@ raf@^3.4.1: dependencies: performance-now "^2.1.0" -ramda@^0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" - integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU= - -ramda@~0.27.1: - version "0.27.2" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.2.tgz#84463226f7f36dc33592f6f4ed6374c48306c3f1" - integrity sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA== +ramda@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.28.0.tgz#acd785690100337e8b063cab3470019be427cc97" + integrity sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA== randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" @@ -18093,16 +17983,6 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" - integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== - dependencies: - bytes "3.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" @@ -18141,22 +18021,6 @@ react-base16-styling@^0.6.0: lodash.flow "^3.3.0" pure-color "^1.2.0" -react-colorful@^5.1.2: - version "5.5.1" - resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.5.1.tgz#29d9c4e496f2ca784dd2bb5053a3a4340cfaf784" - integrity sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg== - -react-cornerstone-viewport@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/react-cornerstone-viewport/-/react-cornerstone-viewport-4.1.2.tgz#1c37645b62b6f4d48f447e2c2c6ba5c295e94f53" - integrity sha512-ys9UHiVjLoDT/RQMvSs/4pgr/vCS+b56pA92RxzzJO30yZA2jvISm5gRO1cbuqlBPlfRHddJTgXHDErI4FAcSQ== - dependencies: - classnames "^2.3.1" - date-fns "^2.23.0" - lodash.debounce "^4.0.8" - prop-types "^15.7.2" - react-resize-detector "^6.7.6" - react-dates@^21.8.0: version "21.8.0" resolved "https://registry.yarnpkg.com/react-dates/-/react-dates-21.8.0.tgz#355c3c7a243a7c29568fe00aca96231e171a5e94" @@ -18178,35 +18042,35 @@ react-dates@^21.8.0: react-with-styles "^4.1.0" react-with-styles-interface-css "^6.0.0" -react-dev-utils@^11.0.1: - version "11.0.4" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a" - integrity sha512-dx0LvIGHcOPtKbeiSUM4jqpBl3TcY7CDjZdfOIcKeznE7BWr9dg0iPG90G5yfVQ+p/rGNMXdbfStvzQZEVEi4A== - dependencies: - "@babel/code-frame" "7.10.4" - address "1.1.2" - browserslist "4.14.2" - chalk "2.4.2" - cross-spawn "7.0.3" - detect-port-alt "1.1.6" - escape-string-regexp "2.0.0" - filesize "6.1.0" - find-up "4.1.0" - fork-ts-checker-webpack-plugin "4.1.6" - global-modules "2.0.0" - globby "11.0.1" - gzip-size "5.1.1" - immer "8.0.1" - is-root "2.1.0" - loader-utils "2.0.0" - open "^7.0.2" - pkg-up "3.1.0" - prompts "2.4.0" - react-error-overlay "^6.0.9" - recursive-readdir "2.2.2" - shell-quote "1.7.2" - strip-ansi "6.0.0" - text-table "0.2.0" +react-dev-utils@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" + integrity sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ== + dependencies: + "@babel/code-frame" "^7.16.0" + address "^1.1.2" + browserslist "^4.18.1" + chalk "^4.1.2" + cross-spawn "^7.0.3" + detect-port-alt "^1.1.6" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" + global-modules "^2.0.0" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" + is-root "^2.1.0" + loader-utils "^3.2.0" + open "^8.4.0" + pkg-up "^3.1.0" + prompts "^2.4.2" + react-error-overlay "^6.0.11" + recursive-readdir "^2.2.2" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" react-dnd-html5-backend@14.0.0: version "14.0.0" @@ -18226,15 +18090,15 @@ react-dnd@14.0.2: fast-deep-equal "^3.1.3" hoist-non-react-statics "^3.3.2" -react-docgen-typescript@^2.0.0: +react-docgen-typescript@^2.1.1: version "2.2.2" resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c" integrity sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg== react-docgen@^5.0.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-5.4.0.tgz#2cd7236720ec2769252ef0421f23250b39a153a1" - integrity sha512-JBjVQ9cahmNlfjMGxWUxJg919xBBKAoy3hgDgKERbR+BcF4ANpDuzWAScC7j27hZfd8sJNmMPOLWo9+vB/XJEQ== + version "5.4.1" + resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-5.4.1.tgz#867168accce39e25095a23a922eaa90722e9d182" + integrity sha512-TZqD1aApirw86NV6tHrmDoxUn8wlinkVyutFarzbdwuhEurAzDN0y5sSj64o+BrHLPqjwpH9tunpfwgy+3Uyww== dependencies: "@babel/core" "^7.7.5" "@babel/generator" "^7.12.11" @@ -18264,14 +18128,6 @@ react-draggable@4.4.3: classnames "^2.2.5" prop-types "^15.6.0" -react-draggable@^4.4.3: - version "4.4.4" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.4.tgz#5b26d9996be63d32d285a426f41055de87e59b2f" - integrity sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA== - dependencies: - clsx "^1.1.1" - prop-types "^15.6.0" - react-dropzone@^10.1.7: version "10.2.2" resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.2.2.tgz#67b4db7459589a42c3b891a82eaf9ade7650b815" @@ -18297,12 +18153,12 @@ react-error-boundary@^3.1.3: dependencies: "@babel/runtime" "^7.12.5" -react-error-overlay@^6.0.9: - version "6.0.10" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" - integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== +react-error-overlay@^6.0.11: + version "6.0.11" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" + integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-fast-compare@^3.0.1, react-fast-compare@^3.1.1, react-fast-compare@^3.2.0: +react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== @@ -18312,10 +18168,10 @@ react-ga@^2.7.0: resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.7.0.tgz#24328f157f31e8cffbf4de74a3396536679d8d7c" integrity sha512-AjC7UOZMvygrWTc2hKxTDvlMXEtbmA0IgJjmkhgmQQ3RkXrWR11xEagLGFGaNyaPnmg24oaIiaNPnEoftUhfXA== -react-helmet-async@^1.0.7: - version "1.2.3" - resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.2.3.tgz#57326a69304ea3293036eafb49475e9ba454cb37" - integrity sha512-mCk2silF53Tq/YaYdkl2sB+/tDoPnaxN7dFS/6ZLJb/rhUY2EWGI5Xj2b4jHppScMqY45MbgPSwTxDchKpZ5Kw== +react-helmet-async@*, react-helmet-async@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.3.0.tgz#7bd5bf8c5c69ea9f02f6083f14ce33ef545c222e" + integrity sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg== dependencies: "@babel/runtime" "^7.12.5" invariant "^2.2.4" @@ -18323,16 +18179,6 @@ react-helmet-async@^1.0.7: react-fast-compare "^3.2.0" shallowequal "^1.1.0" -react-helmet@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" - integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== - dependencies: - object-assign "^4.1.1" - prop-types "^15.7.2" - react-fast-compare "^3.1.1" - react-side-effect "^2.1.0" - react-hot-loader@^4.13.0: version "4.13.0" resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202" @@ -18371,7 +18217,7 @@ react-inspector@^5.1.0: is-dom "^1.0.0" prop-types "^15.0.0" -react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2: +react-is@17.0.2, react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== @@ -18396,18 +18242,17 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-live@^2.2.3: - version "2.4.1" - resolved "https://registry.yarnpkg.com/react-live/-/react-live-2.4.1.tgz#65e674ff9ca9a9a95f83117acc21ffd968aca619" - integrity sha512-r+32f7oV/kBs3QZBRvaT+9vOkQW47UZrDpgwUe5FiIMOl7sdo5pmISgb7Zpj5PGHgY6XQaiXs3FEh+IWw3KbRg== +react-live@2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-live/-/react-live-2.2.3.tgz#260f99194213799f0005e473e7a4154c699d6a7c" + integrity sha512-tpKruvfytNETuzO3o1mrQUj180GVrq35IE8F5gH1NJVPt4szYCx83/dOSCOyjgRhhc3gQvl0pQ3k/CjOjwJkKQ== dependencies: - "@types/buble" "^0.20.0" buble "0.19.6" - core-js "^3.14.0" + core-js "^2.4.1" dom-iterator "^1.0.0" - prism-react-renderer "^1.2.1" - prop-types "^15.7.2" - react-simple-code-editor "^0.11.0" + prism-react-renderer "^1.0.1" + prop-types "^15.5.8" + react-simple-code-editor "^0.10.0" unescape "^1.0.1" react-loadable-ssr-addon-v5-slorber@^1.0.1: @@ -18417,13 +18262,6 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" -react-loadable@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/react-loadable/-/react-loadable-5.5.0.tgz#582251679d3da86c32aae2c8e689c59f1196d8c4" - integrity sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg== - dependencies: - prop-types "^15.5.0" - react-modal@3.11.2: version "3.11.2" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4" @@ -18452,23 +18290,6 @@ react-outside-click-handler@^1.2.4, react-outside-click-handler@^1.3.0: object.values "^1.1.0" prop-types "^15.7.2" -react-popper-tooltip@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-3.1.1.tgz#329569eb7b287008f04fcbddb6370452ad3f9eac" - integrity sha512-EnERAnnKRptQBJyaee5GJScWNUKQPDD2ywvzZyUjst/wj5U64C8/CnSYLNEmP2hG0IJ3ZhtDxE8oDN+KOyavXQ== - dependencies: - "@babel/runtime" "^7.12.5" - "@popperjs/core" "^2.5.4" - react-popper "^2.2.4" - -react-popper@^2.2.4: - version "2.2.5" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96" - integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw== - dependencies: - react-fast-compare "^3.0.1" - warning "^4.0.2" - react-portal@^4.2.0: version "4.2.2" resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.2.tgz#bff1e024147d6041ba8c530ffc99d4c8248f49fa" @@ -18481,17 +18302,6 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== -react-resize-detector@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-4.2.3.tgz#7df258668a30bdfd88e655bbdb27db7fd7b23127" - integrity sha512-4AeS6lxdz2KOgDZaOVt1duoDHrbYwSrUX32KeM9j6t9ISyRphoJbTRCMS1aPFxZHFqcCGLT1gMl3lEcSWZNW0A== - dependencies: - lodash "^4.17.15" - lodash-es "^4.17.15" - prop-types "^15.7.2" - raf-schd "^4.0.2" - resize-observer-polyfill "^1.5.1" - react-resize-detector@^6.7.6: version "6.7.8" resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-6.7.8.tgz#318c85d1335e50f99d4fb8eb9ec34e066db597d0" @@ -18509,19 +18319,19 @@ react-router-config@^5.1.1: "@babel/runtime" "^7.1.2" react-router-dom@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363" - integrity sha512-ObVBLjUZsphUUMVycibxgMdh5jJ1e3o+KpAZBVeHcNQZ4W+uUGGWsokurzlF4YOldQYRQL4y6yFRWM4m3svmuQ== + version "5.3.3" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.3.tgz#8779fc28e6691d07afcaf98406d3812fe6f11199" + integrity sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng== dependencies: "@babel/runtime" "^7.12.13" history "^4.9.0" loose-envify "^1.3.1" prop-types "^15.6.2" - react-router "5.2.1" + react-router "5.3.3" tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router-dom@^6.0.0, react-router-dom@^6.2.1: +react-router-dom@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== @@ -18529,10 +18339,10 @@ react-router-dom@^6.0.0, react-router-dom@^6.2.1: history "^5.2.0" react-router "6.3.0" -react-router@5.2.1, react-router@^5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d" - integrity sha512-lIboRiOtDLFdg1VTemMwud9vRVuOCZmUIT/7lUoZiSpPODiiH1UQlfXy+vPLC/7IWdFYnhRwAyNqA/+I7wnvKQ== +react-router@5.3.3, react-router@^5.2.0: + version "5.3.3" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.3.tgz#8e3841f4089e728cf82a429d92cdcaa5e4a3a288" + integrity sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w== dependencies: "@babel/runtime" "^7.12.13" history "^4.9.0" @@ -18545,7 +18355,7 @@ react-router@5.2.1, react-router@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@6.3.0, react-router@^6.0.0, react-router@^6.2.1: +react-router@6.3.0, react-router@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -18566,45 +18376,30 @@ react-select@3.0.8: react-input-autosize "^2.2.2" react-transition-group "^2.2.1" -react-side-effect@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" - integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== - -react-simple-code-editor@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.11.0.tgz#bb57c7c29b570f2ab229872599eac184f5bc673c" - integrity sha512-xGfX7wAzspl113ocfKQAR8lWPhavGWHL3xSzNLeseDRHysT+jzRBi/ExdUqevSMos+7ZtdfeuBOXtgk9HTwsrw== - -react-sizeme@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.2.tgz#4a2f167905ba8f8b8d932a9e35164e459f9020e4" - integrity sha512-xOIAOqqSSmKlKFJLO3inBQBdymzDuXx4iuwkNcJmC96jeiOg5ojByvL+g3MW9LPEsojLbC6pf68zOfobK8IPlw== - dependencies: - element-resize-detector "^1.2.2" - invariant "^2.2.4" - shallowequal "^1.1.0" - throttle-debounce "^3.0.1" +react-simple-code-editor@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/react-simple-code-editor/-/react-simple-code-editor-0.10.0.tgz#73e7ac550a928069715482aeb33ccba36efe2373" + integrity sha512-bL5W5mAxSW6+cLwqqVWY47Silqgy2DKDTR4hDBrLrUqC5BXc29YVx17l2IZk5v36VcDEq1Bszu2oHm1qBwKqBA== -react-syntax-highlighter@^13.5.3: - version "13.5.3" - resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-13.5.3.tgz#9712850f883a3e19eb858cf93fad7bb357eea9c6" - integrity sha512-crPaF+QGPeHNIblxxCdf2Lg936NAHKhNhuMzRL3F9ct6aYXL3NcZtCL0Rms9+qVo6Y1EQLdXGypBNSbPL/r+qg== +react-syntax-highlighter@^15.4.5: + version "15.5.0" + resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" + integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg== dependencies: "@babel/runtime" "^7.3.1" - highlight.js "^10.1.1" - lowlight "^1.14.0" - prismjs "^1.21.0" - refractor "^3.1.0" + highlight.js "^10.4.1" + lowlight "^1.17.0" + prismjs "^1.27.0" + refractor "^3.6.0" -react-textarea-autosize@^8.3.0, react-textarea-autosize@^8.3.2: - version "8.3.3" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" - integrity sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ== +react-textarea-autosize@^8.3.2: + version "8.3.4" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz#270a343de7ad350534141b02c9cb78903e553524" + integrity sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ== dependencies: "@babel/runtime" "^7.10.2" - use-composed-ref "^1.0.0" - use-latest "^1.0.0" + use-composed-ref "^1.3.0" + use-latest "^1.2.1" react-transition-group@^2.2.1: version "2.9.0" @@ -18781,7 +18576,7 @@ read@1, read@~1.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -18816,7 +18611,7 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -reading-time@^1.3.0: +reading-time@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.5.0.tgz#d2a7f1b6057cb2e169beaf87113cc3411b5bc5bb" integrity sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg== @@ -18842,7 +18637,7 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" -recursive-readdir@2.2.2: +recursive-readdir@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== @@ -18873,13 +18668,6 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redeyed@~2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b" - integrity sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs= - dependencies: - esprima "~4.0.0" - reduce-css-calc@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" @@ -18889,9 +18677,9 @@ reduce-css-calc@^2.1.8: postcss-value-parser "^3.3.0" redux@^4.0.5: - version "4.1.2" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" - integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== + version "4.2.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" + integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== dependencies: "@babel/runtime" "^7.9.2" @@ -18900,7 +18688,7 @@ reflect.ownkeys@^0.2.0: resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= -refractor@^3.1.0: +refractor@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA== @@ -18928,7 +18716,7 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== @@ -18948,13 +18736,14 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" - integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" + functions-have-names "^1.2.2" regexpp@^3.0.0, regexpp@^3.1.0: version "3.2.0" @@ -19068,7 +18857,7 @@ remark-admonitions@^1.2.1: unified "^8.4.2" unist-util-visit "^2.0.1" -remark-emoji@^2.1.0: +remark-emoji@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7" integrity sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w== @@ -19212,16 +19001,6 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request-promise@^4.2.2: - version "4.2.6" - resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.6.tgz#7e7e5b9578630e6f598e3813c0f8eb342a27f0a2" - integrity sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ== - dependencies: - bluebird "^3.5.0" - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - request@^2.87.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -19341,7 +19120,15 @@ resolve@^2.0.0-next.3: resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== dependencies: - is-core-module "^2.2.0" + is-core-module "^2.2.0" + path-parse "^1.0.6" + +resolve@~1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" path-parse "^1.0.6" responselike@^1.0.2: @@ -19351,14 +19138,6 @@ responselike@^1.0.2: dependencies: lowercase-keys "^1.0.0" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" - integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -19380,21 +19159,11 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry-axios@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/retry-axios/-/retry-axios-1.0.2.tgz#f1b5895ad0ef69656036c48fd7952f72340ed772" - integrity sha512-PeR6ZVYscfOHrbN3A6EiP8M6UlseHpDkwVDsT6YMcZH0qyMubuFIq6gwydn+ZkvBzry3xmAZwZ3pW1zmJbvLOA== - retry@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - retry@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" @@ -19405,6 +19174,11 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -19415,7 +19189,7 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: +rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -19448,9 +19222,9 @@ rollup-plugin-terser@^7.0.0: terser "^5.0.0" rollup@^2.43.1: - version "2.70.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e" - integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA== + version "2.75.5" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.5.tgz#7985c1962483235dd07966f09fdad5c5f89f16d0" + integrity sha512-JzNlJZDison3o2mOxVmb44Oz7t74EfSd1SQrplQk0wSaXV7uLQXtVdHbxlcT3w+8tZ1TL4r/eLfc7nAbz38BBA== optionalDependencies: fsevents "~2.3.2" @@ -19459,12 +19233,12 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== -rtl-detect@^1.0.3: +rtl-detect@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== -rtlcss@^3.1.2: +rtlcss@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== @@ -19498,14 +19272,14 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.6.3: +rxjs@^6.3.3, rxjs@^6.4.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" -rxjs@^7.1.0, rxjs@^7.5.5: +rxjs@^7.1.0, rxjs@^7.5.1, rxjs@^7.5.4, rxjs@^7.5.5: version "7.5.5" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== @@ -19534,11 +19308,6 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -safe-stable-stringify@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" - integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== - "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -19559,7 +19328,7 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1, sax@~1.2.4: +sax@^1.2.4, sax@~1.2.1, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -19626,19 +19395,17 @@ section-matter@^1.0.0: extend-shallow "^2.0.1" kind-of "^6.0.0" +seedrandom@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selfsigned@^1.10.8: - version "1.10.14" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.14.tgz#ee51d84d9dcecc61e07e4aba34f229ab525c1574" - integrity sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA== - dependencies: - node-forge "^0.10.0" - -selfsigned@^2.0.0: +selfsigned@^2.0.0, selfsigned@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.1.tgz#8b2df7fa56bf014d19b6007655fff209c0ef0a56" integrity sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ== @@ -19672,31 +19439,31 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semve resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" - integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: - lru-cache "^7.4.0" + lru-cache "^6.0.0" -send@0.17.2: - version "0.17.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" - integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== dependencies: debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" + depd "2.0.0" + destroy "1.2.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "1.8.1" + http-errors "2.0.0" mime "1.6.0" ms "2.1.3" - on-finished "~2.3.0" + on-finished "2.4.1" range-parser "~1.2.1" - statuses "~1.5.0" + statuses "2.0.1" serialize-javascript@^4.0.0: version "4.0.0" @@ -19757,15 +19524,15 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.14.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" - integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.2" + send "0.18.0" serve@^11.1.0: version "11.3.2" @@ -19787,6 +19554,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" @@ -19820,7 +19592,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shader-loader@^1.3.1: +shader-loader@1.3.1, shader-loader@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/shader-loader/-/shader-loader-1.3.1.tgz#2d87808c088bdd172ce577490b41db4e6f01535f" integrity sha512-dt8F9K0x4rjmaFyHh7rNDfpt4LUiR64zhNIEwp2WbE99B3z4ALuvvmhftkElg93dUD6sTmv/aXa/z9SJiEddcA== @@ -19839,17 +19611,17 @@ shallowequal@^1.1.0: resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== -sharp@^0.28.2: - version "0.28.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.28.3.tgz#ecd74cefd020bee4891bb137c9850ee2ce277a8b" - integrity sha512-21GEP45Rmr7q2qcmdnjDkNP04Ooh5v0laGS5FDpojOO84D1DJwUijLiSq8XNNM6e8aGXYtoYRh3sVNdm8NodMA== - dependencies: - color "^3.1.3" - detect-libc "^1.0.3" - node-addon-api "^3.2.0" - prebuild-install "^6.1.2" - semver "^7.3.5" - simple-get "^3.1.0" +sharp@^0.30.4: + version "0.30.6" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.6.tgz#02264e9826b5f1577509f70bb627716099778873" + integrity sha512-lSdVxFxcndzcXggDrak6ozdGJgmIgES9YVZWtAFrwi+a/H5vModaf51TghBtMPw+71sLxUsTy2j+aB7qLIODQg== + dependencies: + color "^4.2.3" + detect-libc "^2.0.1" + node-addon-api "^5.0.0" + prebuild-install "^7.1.0" + semver "^7.3.7" + simple-get "^4.0.1" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -19877,12 +19649,12 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" - integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== +shell-quote@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" + integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== -shelljs@^0.8.4: +shelljs@0.8.5, shelljs@^0.8.5: version "0.8.5" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== @@ -19915,12 +19687,12 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^3.0.3, simple-get@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55" - integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== +simple-get@^4.0.0, simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== dependencies: - decompress-response "^4.2.0" + decompress-response "^6.0.0" once "^1.3.1" simple-concat "^1.0.0" @@ -19945,7 +19717,7 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -sitemap@^7.0.0: +sitemap@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== @@ -19965,11 +19737,25 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -20019,18 +19805,7 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sockjs-client@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.6.0.tgz#e0277b8974558edcb557eafc7d3027ef6128d865" - integrity sha512-qVHJlyfdHFht3eBFZdKEXKTlb7I4IV41xnVNo8yUKA1UHcPJwgW2SvTq9LhnjjCywSkSK7c/e4nghU0GOoMCRQ== - dependencies: - debug "^3.2.7" - eventsource "^1.1.0" - faye-websocket "^0.11.4" - inherits "^2.0.4" - url-parse "^1.5.10" - -sockjs@^0.3.21: +sockjs@^0.3.21, sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== @@ -20060,6 +19835,13 @@ sort-css-media-queries@2.0.4: resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.0.4.tgz#b2badfa519cb4a938acbc6d3aaa913d4949dc908" integrity sha512-PAIsEK/XupCQwitjv7XxoMvYhT7EAfyzI3hsy/MyDgTvc+Ft55ctdkctJLOy6cQejaIC+zjpUL4djFVm2ivOOw== +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" @@ -20111,7 +19893,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3, source-map@~0.7.2: +source-map@^0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -20133,6 +19915,11 @@ space-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== +spark-md5@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" + integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -20266,11 +20053,6 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= - stack-utils@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.5.tgz#a19b0b01947e0029c8e451d5d61a498f5bb1471b" @@ -20314,22 +20096,15 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -std-env@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-2.3.1.tgz#d42271908819c243f8defc77a140fc1fcee336a1" - integrity sha512-eOsoKTWnr6C8aWrqJJ2KAReXoa7Vn5Ywyw6uCXgA/xDhxPoaIsBa5aNJmISY04dLwXPBnDHW4diGM7Sn5K4R/g== - dependencies: - ci-info "^3.1.1" - std-env@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.0.1.tgz#bc4cbc0e438610197e34c2d79c3df30b491f5182" - integrity sha512-mC1Ps9l77/97qeOZc+HrOL7TIaOboHqMZ24dGVQrlxFcpPpfCHpH+qfUT7Dz+6mlG8+JPA1KfBQo19iC/+Ngcw== + version "3.1.1" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.1.1.tgz#1f19c4d3f6278c52efd08a94574a2a8d32b7d092" + integrity sha512-/c645XdExBypL01TpFKiG/3RAa/Qmu+zRi0MwAmrdEkwHNuN0ebo8ccAXBBDa5Z0QOJgBskUIbuCK91x0sCVEw== stealthy-require@^1.1.1: version "1.1.1" @@ -20341,6 +20116,14 @@ store2@^2.12.0: resolved "https://registry.yarnpkg.com/store2/-/store2-2.13.2.tgz#01ad8802ca5b445b9c316b55e72645c13a3cd7e3" integrity sha512-CMtO2Uneg3SAz/d6fZ/6qbqqQHi2ynq6/KzMD/26gTkiEShCcpqFfTHgOxsE0egAq6SX3FmN4CeSqn8BzXQkJg== +stream-browserify@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -20380,6 +20163,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -20438,7 +20226,16 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -"string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.6: +string-width@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +"string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg== @@ -20470,21 +20267,23 @@ string.prototype.padstart@^3.0.0: define-properties "^1.1.3" es-abstract "^1.19.1" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -20509,13 +20308,6 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -strip-ansi@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -20544,7 +20336,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.0: +strip-ansi@^7.0.0, strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== @@ -20671,7 +20463,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -20685,28 +20477,20 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -supports-color@^8.0.0, supports-color@^8.1.0: +supports-color@^8.0.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -20749,7 +20533,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -svgo@^2.7.0: +svgo@^2.5.0, svgo@^2.7.0: version "2.8.0" resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== @@ -20891,7 +20675,7 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -telejson@^5.1.0, telejson@^5.3.2, telejson@^5.3.3: +telejson@^5.1.0: version "5.3.3" resolved "https://registry.yarnpkg.com/telejson/-/telejson-5.3.3.tgz#fa8ca84543e336576d8734123876a9f02bf41d2e" integrity sha512-PjqkJZpzEggA9TBpVtJi1LVptP7tYtXB6rEubwlHap76AMjzvOdKX41CxyaW7ahhzDU1aftXnMCx5kAPDZTQBA== @@ -20905,6 +20689,20 @@ telejson@^5.1.0, telejson@^5.3.2, telejson@^5.3.3: lodash "^4.17.21" memoizerific "^1.11.3" +telejson@^6.0.8: + version "6.0.8" + resolved "https://registry.yarnpkg.com/telejson/-/telejson-6.0.8.tgz#1c432db7e7a9212c1fbd941c3e5174ec385148f7" + integrity sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg== + dependencies: + "@types/is-function" "^1.0.0" + global "^4.4.0" + is-function "^1.0.2" + is-regex "^1.1.2" + is-symbol "^1.0.3" + isobject "^4.0.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -20974,7 +20772,7 @@ terser-webpack-plugin@^4.2.3: terser "^5.3.4" webpack-sources "^1.4.3" -terser-webpack-plugin@^5.0.3, terser-webpack-plugin@^5.1.1, terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.1.4: +terser-webpack-plugin@^5.0.3, terser-webpack-plugin@^5.1.1, terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.1.4, terser-webpack-plugin@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== @@ -20995,13 +20793,13 @@ terser@^4.1.2, terser@^4.6.3: source-map-support "~0.5.12" terser@^5.0.0, terser@^5.10.0, terser@^5.3.4, terser@^5.7.2: - version "5.12.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.12.1.tgz#4cf2ebed1f5bceef5c83b9f60104ac4a78b49e9c" - integrity sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ== + version "5.14.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.0.tgz#eefeec9af5153f55798180ee2617f390bdd285e2" + integrity sha512-JC6qfIEkPBd9j1SMO3Pfn+A6w2kQV54tv+ABQLgZr7dA3k/DL/OBoYSWxzVpZev3J+bUHXfr55L8Mox7AaNo6g== dependencies: + "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" test-exclude@^5.2.3: @@ -21028,12 +20826,7 @@ text-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -text-table@0.2.0, text-table@^0.2.0: +text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= @@ -21057,11 +20850,6 @@ throat@^4.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= -throttle-debounce@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" - integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== - throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -21107,11 +20895,6 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -timm@^1.6.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f" - integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw== - timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" @@ -21127,11 +20910,6 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinycolor2@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" - integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -21198,11 +20976,6 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -toggle-selection@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" - integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -21258,11 +21031,6 @@ trim@0.0.1: resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= -triple-beam@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" - integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== - trough@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" @@ -21288,15 +21056,22 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1, tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" tty-browserify@0.0.0: version "0.0.0" @@ -21364,6 +21139,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^2.5.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.13.0.tgz#d1ecee38af29eb2e863b22299a3d68ef30d2abfb" + integrity sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -21384,15 +21164,20 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.2.3: + version "4.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4" + integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== + ua-parser-js@^0.7.30: version "0.7.31" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== uglify-js@^3.1.4: - version "3.15.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.3.tgz#9aa82ca22419ba4c0137642ba0df800cb06e0471" - integrity sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg== + version "3.15.5" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.5.tgz#2b10f9e0bfb3f5c15a8e8404393b6361eaeb33b3" + integrity sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ== uid-number@0.0.6: version "0.0.6" @@ -21404,24 +21189,16 @@ umask@^1.1.0: resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -unbzip2-stream@^1.3.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - unescape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unescape/-/unescape-1.0.1.tgz#956e430f61cad8a4d57d82c518f5e6cc5d0dda96" @@ -21578,7 +21355,7 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" -unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.1, unist-util-visit@^2.0.2, unist-util-visit@^2.0.3: +unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.1, unist-util-visit@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== @@ -21627,6 +21404,13 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +untildify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0" + integrity sha1-F+soB5h/dpUunASF/DEdBqgmouA= + dependencies: + os-homedir "^1.0.0" + untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -21701,14 +21485,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3, url-parse@^1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -21717,35 +21493,28 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -use-composed-ref@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.2.1.tgz#9bdcb5ccd894289105da2325e1210079f56bf849" - integrity sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw== +use-composed-ref@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" + integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== -use-isomorphic-layout-effect@^1.0.0: +use-isomorphic-layout-effect@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== -use-latest@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232" - integrity sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw== +use-latest@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" + integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== dependencies: - use-isomorphic-layout-effect "^1.0.0" + use-isomorphic-layout-effect "^1.1.1" use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -utif@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759" - integrity sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg== - dependencies: - pako "^1.0.5" - util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -21848,14 +21617,14 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -v8-to-istanbul@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" - integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== +v8-to-istanbul@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz#be0dae58719fc53cb97e5c7ac1d7e6d4f5b19511" + integrity sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw== dependencies: + "@jridgewell/trace-mapping" "^0.3.7" "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" - source-map "^0.7.3" validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: version "3.0.4" @@ -21882,7 +21651,7 @@ value-equal@^1.0.1: resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== -vary@^1, vary@~1.1.2: +vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -21957,23 +21726,16 @@ wait-on@6.0.0: minimist "^1.2.5" rxjs "^7.1.0" -wait-on@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-5.3.0.tgz#584e17d4b3fe7b46ac2b9f8e5e102c005c2776c7" - integrity sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg== +wait-on@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" + integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== dependencies: - axios "^0.21.1" - joi "^17.3.0" + axios "^0.25.0" + joi "^17.6.0" lodash "^4.17.21" minimist "^1.2.5" - rxjs "^6.6.3" - -walk@^2.3.14: - version "2.3.15" - resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.15.tgz#1b4611e959d656426bc521e2da5db3acecae2424" - integrity sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg== - dependencies: - foreachasync "^3.0.0" + rxjs "^7.5.4" walker@^1.0.7, walker@~1.0.5: version "1.0.8" @@ -21982,7 +21744,7 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.12" -warning@^4.0.2, warning@^4.0.3: +warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== @@ -22035,9 +21797,14 @@ web-namespaces@^1.0.0, web-namespaces@^1.1.2: integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== web-streams-polyfill@^3.0.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" - integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + +webgl-constants@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/webgl-constants/-/webgl-constants-1.1.1.tgz#f9633ee87fea56647a60b9ce735cbdfb891c6855" + integrity sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg== webidl-conversions@^3.0.0: version "3.0.1" @@ -22049,7 +21816,7 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-bundle-analyzer@^4.4.2: +webpack-bundle-analyzer@^4.4.2, webpack-bundle-analyzer@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5" integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ== @@ -22082,7 +21849,7 @@ webpack-cli@^4.7.2: rechoir "^0.7.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^3.7.2, webpack-dev-middleware@^3.7.3: +webpack-dev-middleware@^3.7.3: version "3.7.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== @@ -22105,13 +21872,13 @@ webpack-dev-middleware@^4.1.0: range-parser "^1.2.1" schema-utils "^3.0.0" -webpack-dev-middleware@^5.3.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" - integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== +webpack-dev-middleware@^5.3.0, webpack-dev-middleware@^5.3.1: + version "5.3.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" + integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== dependencies: colorette "^2.0.10" - memfs "^3.4.1" + memfs "^3.4.3" mime-types "^2.1.31" range-parser "^1.2.1" schema-utils "^4.0.0" @@ -22151,44 +21918,39 @@ webpack-dev-server@4.7.3: webpack-dev-middleware "^5.3.0" ws "^8.1.0" -webpack-dev-server@^3.11.2: - version "3.11.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.3.tgz#8c86b9d2812bf135d3c9bce6f07b718e30f7c3d3" - integrity sha512-3x31rjbEQWKMNzacUZRE6wXvUFuGpH7vr0lIEbYpMAG9BOxi0928QU1BBswOAP3kg3H1O4hiS+sq4YyAn6ANnA== +webpack-dev-server@^4.8.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.1.tgz#184607b0287c791aeaa45e58e8fe75fcb4d7e2a8" + integrity sha512-CTMfu2UMdR/4OOZVHRpdy84pNopOuigVIsRbGX3LVDMWNP8EUgC5mUBMErbwBlHTEX99ejZJpVqrir6EXAEajA== dependencies: - ansi-html-community "0.0.8" - bonjour "^3.5.0" - chokidar "^2.1.8" + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.1" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^1.6.0" - debug "^4.1.1" - del "^4.1.1" - express "^4.17.1" - html-entities "^1.3.1" - http-proxy-middleware "0.19.1" - import-local "^2.0.0" - internal-ip "^4.3.0" - ip "^1.1.5" - is-absolute-url "^3.0.3" - killable "^1.0.1" - loglevel "^1.6.8" - opn "^5.5.0" - p-retry "^3.0.1" - portfinder "^1.0.26" - schema-utils "^1.0.0" - selfsigned "^1.10.8" - semver "^6.3.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.0.1" serve-index "^1.9.1" - sockjs "^0.3.21" - sockjs-client "^1.5.0" + sockjs "^0.3.24" spdy "^4.0.2" - strip-ansi "^3.0.1" - supports-color "^6.1.0" - url "^0.11.0" - webpack-dev-middleware "^3.7.2" - webpack-log "^2.0.0" - ws "^6.2.1" - yargs "^13.3.2" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" webpack-filter-warnings-plugin@^1.2.1: version "1.2.1" @@ -22283,10 +22045,10 @@ webpack@4: watchpack "^1.7.4" webpack-sources "^1.4.1" -webpack@^5.40.0, webpack@^5.50.0, webpack@^5.9.0: - version "5.71.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.71.0.tgz#b01fcf379570b8c5ee06ca06c829ca168c951884" - integrity sha512-g4dFT7CFG8LY0iU5G8nBL6VlkT21Z7dcYDpJAEJV5Q1WLb9UwnFbrem1k7K52ILqEmomN7pnzWFxxE6SlDY56A== +"webpack@>=4.43.0 <6.0.0", webpack@^5.50.0, webpack@^5.72.0, webpack@^5.9.0: + version "5.72.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.1.tgz#3500fc834b4e9ba573b9f430b2c0a61e1bb57d13" + integrity sha512-dXG5zXCLspQR4krZVR6QgajnZOjW2K/djHvdcRaDQvsjV9z9vaW6+ja5dZOYbqBBjF6kGXka/2ZyxNdc+8Jung== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" @@ -22297,13 +22059,13 @@ webpack@^5.40.0, webpack@^5.50.0, webpack@^5.9.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.2" + enhanced-resolve "^5.9.3" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" graceful-fs "^4.2.9" - json-parse-better-errors "^1.0.2" + json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" @@ -22313,7 +22075,7 @@ webpack@^5.40.0, webpack@^5.50.0, webpack@^5.9.0: watchpack "^2.3.1" webpack-sources "^3.2.3" -webpackbar@^5.0.0-3: +webpackbar@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== @@ -22337,6 +22099,11 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +webworker-promise@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/webworker-promise/-/webworker-promise-0.5.0.tgz#eb1aa89f26ca6a49765f332668644d74c2c8e320" + integrity sha512-14iR79jHAV7ozwvbfif+3wCaApT3I1g8Lo0rJZrwAu6wxZGx/08Y8KXz6as6ZLNUEEufeiEBBYrqyDBClXOsEw== + whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -22397,16 +22164,16 @@ which-module@^2.0.0: integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= which-typed-array@^1.1.2: - version "1.1.7" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" - integrity sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw== + version "1.1.8" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.8.tgz#0cfd53401a6f334d90ed1125754a42ed663eb01f" + integrity sha512-Jn4e5PItbcAHyLoRDwvPj1ypu27DJbtdYXUa5zsinrUx77Uvfb0cXwwnGMTn7cjUfhhqgVQnVJCwF+7cgU7tpw== dependencies: available-typed-arrays "^1.0.5" call-bind "^1.0.2" - es-abstract "^1.18.5" - foreach "^2.0.5" + es-abstract "^1.20.0" + for-each "^0.3.3" has-tostringtag "^1.0.0" - is-typed-array "^1.1.7" + is-typed-array "^1.1.9" which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" @@ -22429,7 +22196,7 @@ wide-align@^1.1.0, wide-align@^1.1.2: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -widest-line@^2.0.0, widest-line@^2.0.1: +widest-line@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== @@ -22443,6 +22210,13 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -22455,31 +22229,6 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" -winston-transport@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" - integrity sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q== - dependencies: - logform "^2.3.2" - readable-stream "^3.6.0" - triple-beam "^1.3.0" - -winston@^3.0.0: - version "3.7.2" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.7.2.tgz#95b4eeddbec902b3db1424932ac634f887c400b1" - integrity sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng== - dependencies: - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.4.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.5.0" - word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -22490,25 +22239,25 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -workbox-background-sync@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.2.tgz#28be9bf89b8e4e0379d45903280c7c12f4df836f" - integrity sha512-EjG37LSMDJ1TFlFg56wx6YXbH4/NkG09B9OHvyxx+cGl2gP5OuOzsCY3rOPJSpbcz6jpuA40VIC3HzSD4OvE1g== +workbox-background-sync@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.3.tgz#7c66c1836aeca6f3762dc48d17a1852a33b3168c" + integrity sha512-0DD/V05FAcek6tWv9XYj2w5T/plxhDSpclIcAGjA/b7t/6PdaRkQ7ZgtAX6Q/L7kV7wZ8uYRJUoH11VjNipMZw== dependencies: idb "^6.1.4" - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-broadcast-update@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.2.tgz#b1f32bb40a9dcb5b05ca27e09fb7c01a0a126182" - integrity sha512-DjJYraYnprTZE/AQNoeogaxI1dPuYmbw+ZJeeP8uXBSbg9SNv5wLYofQgywXeRepv4yr/vglMo9yaHUmBMc+4Q== +workbox-broadcast-update@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.3.tgz#fc2ad79cf507e22950cda9baf1e9a0ccc43f31bc" + integrity sha512-4AwCIA5DiDrYhlN+Miv/fp5T3/whNmSL+KqhTwRBTZIL6pvTgE4lVuRzAt1JltmqyMcQ3SEfCdfxczuI4kwFQg== dependencies: - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-build@6.5.2, workbox-build@^6.1.1: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.2.tgz#774faafd84b1dc94b74739ceb5d8ff367748523b" - integrity sha512-TVi4Otf6fgwikBeMpXF9n0awHfZTMNu/nwlMIT9W+c13yvxkmDFMPb7vHYK6RUmbcxwPnz4I/R+uL76+JxG4JQ== +workbox-build@6.5.3, workbox-build@^6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.3.tgz#38e3f286d63d2745bff4d1478bb3a6ab5c8b1170" + integrity sha512-8JNHHS7u13nhwIYCDea9MNXBNPHXCs5KDZPKI/ZNTr3f4sMGoD7hgFGecbyjX1gw4z6e9bMpMsOEJNyH5htA/w== dependencies: "@apideck/better-ajv-errors" "^0.3.1" "@babel/core" "^7.11.1" @@ -22532,132 +22281,132 @@ workbox-build@6.5.2, workbox-build@^6.1.1: strip-comments "^2.0.1" tempy "^0.6.0" upath "^1.2.0" - workbox-background-sync "6.5.2" - workbox-broadcast-update "6.5.2" - workbox-cacheable-response "6.5.2" - workbox-core "6.5.2" - workbox-expiration "6.5.2" - workbox-google-analytics "6.5.2" - workbox-navigation-preload "6.5.2" - workbox-precaching "6.5.2" - workbox-range-requests "6.5.2" - workbox-recipes "6.5.2" - workbox-routing "6.5.2" - workbox-strategies "6.5.2" - workbox-streams "6.5.2" - workbox-sw "6.5.2" - workbox-window "6.5.2" - -workbox-cacheable-response@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.2.tgz#d9252eb99f0d0fceb70f63866172f4eaac56a3e8" - integrity sha512-UnHGih6xqloV808T7ve1iNKZMbpML0jGLqkkmyXkJbZc5j16+HRSV61Qrh+tiq3E3yLvFMGJ3AUBODOPNLWpTg== + workbox-background-sync "6.5.3" + workbox-broadcast-update "6.5.3" + workbox-cacheable-response "6.5.3" + workbox-core "6.5.3" + workbox-expiration "6.5.3" + workbox-google-analytics "6.5.3" + workbox-navigation-preload "6.5.3" + workbox-precaching "6.5.3" + workbox-range-requests "6.5.3" + workbox-recipes "6.5.3" + workbox-routing "6.5.3" + workbox-strategies "6.5.3" + workbox-streams "6.5.3" + workbox-sw "6.5.3" + workbox-window "6.5.3" + +workbox-cacheable-response@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.3.tgz#b1f8c2bc599a7be8f7e3c262535629c558738e47" + integrity sha512-6JE/Zm05hNasHzzAGKDkqqgYtZZL2H06ic2GxuRLStA4S/rHUfm2mnLFFXuHAaGR1XuuYyVCEey1M6H3PdZ7SQ== dependencies: - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-core@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.2.tgz#f5e06a22c6cb4651d3e13107443d972fdbd47364" - integrity sha512-IlxLGQf+wJHCR+NM0UWqDh4xe/Gu6sg2i4tfZk6WIij34IVk9BdOQgi6WvqSHd879jbQIUgL2fBdJUJyAP5ypQ== +workbox-core@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.3.tgz#bca038a9ef0d7a634a6db2a60f45313ed22ac249" + integrity sha512-Bb9ey5n/M9x+l3fBTlLpHt9ASTzgSGj6vxni7pY72ilB/Pb3XtN+cZ9yueboVhD5+9cNQrC9n/E1fSrqWsUz7Q== -workbox-expiration@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.2.tgz#ee6ed755a220a0b375d67831f9237e4dcbccb59c" - integrity sha512-5Hfp0uxTZJrgTiy9W7AjIIec+9uTOtnxY/tRBm4DbqcWKaWbVTa+izrKzzOT4MXRJJIJUmvRhWw4oo8tpmMouw== +workbox-expiration@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.3.tgz#efc0811f371a2ede1052b9de1c4f072b71d50503" + integrity sha512-jzYopYR1zD04ZMdlbn/R2Ik6ixiXbi15c9iX5H8CTi6RPDz7uhvMLZPKEndZTpfgmUk8mdmT9Vx/AhbuCl5Sqw== dependencies: idb "^6.1.4" - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-google-analytics@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.2.tgz#a79fa7a40824873baaa333dcd72d1fdf1c53adf5" - integrity sha512-8SMar+N0xIreP5/2we3dwtN1FUmTMScoopL86aKdXBpio8vXc8Oqb5fCJG32ialjN8BAOzDqx/FnGeCtkIlyvw== +workbox-google-analytics@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.3.tgz#cc8c3a61f449131660a4ed2f5362d9a3599b18fe" + integrity sha512-3GLCHotz5umoRSb4aNQeTbILETcrTVEozSfLhHSBaegHs1PnqCmN0zbIy2TjTpph2AGXiNwDrWGF0AN+UgDNTw== dependencies: - workbox-background-sync "6.5.2" - workbox-core "6.5.2" - workbox-routing "6.5.2" - workbox-strategies "6.5.2" + workbox-background-sync "6.5.3" + workbox-core "6.5.3" + workbox-routing "6.5.3" + workbox-strategies "6.5.3" -workbox-navigation-preload@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.2.tgz#ffb3d9d5cdb881a3824851707da221dbb0bb3f23" - integrity sha512-iqDNWWMswjCsZuvGFDpcX1Z8InBVAlVBELJ28xShsWWntALzbtr0PXMnm2WHkXCc56JimmGldZi1N5yDPiTPOg== +workbox-navigation-preload@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.3.tgz#81b74f598b11aa07e2cf1c21af7a826a4f0f70b3" + integrity sha512-bK1gDFTc5iu6lH3UQ07QVo+0ovErhRNGvJJO/1ngknT0UQ702nmOUhoN9qE5mhuQSrnK+cqu7O7xeaJ+Rd9Tmg== dependencies: - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-precaching@6.5.2, workbox-precaching@^6.1.1: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.2.tgz#a3117b4d3eb61ce8d01b9dfc063c48155bd7f9d3" - integrity sha512-OZAlQ8AAT20KugGKKuJMHdQ8X1IyNQaLv+mPTHj+8Dmv8peBq5uWNzs4g/1OSFmXsbXZ6a1CBC6YtQWVPhJQ9w== +workbox-precaching@6.5.3, workbox-precaching@^6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.3.tgz#c870312b2ef901d790ab9e48da084e776c62af47" + integrity sha512-sjNfgNLSsRX5zcc63H/ar/hCf+T19fRtTqvWh795gdpghWb5xsfEkecXEvZ8biEi1QD7X/ljtHphdaPvXDygMQ== dependencies: - workbox-core "6.5.2" - workbox-routing "6.5.2" - workbox-strategies "6.5.2" + workbox-core "6.5.3" + workbox-routing "6.5.3" + workbox-strategies "6.5.3" -workbox-range-requests@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.2.tgz#b8b7e5b5830fecc22f0a1d8815457921df2e5bf9" - integrity sha512-zi5VqF1mWqfCyJLTMXn1EuH/E6nisqWDK1VmOJ+TnjxGttaQrseOhMn+BMvULFHeF8AvrQ0ogfQ6bSv0rcfAlg== +workbox-range-requests@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.3.tgz#e624ac82ff266a5e4f236d055797def07949d941" + integrity sha512-pGCP80Bpn/0Q0MQsfETSfmtXsQcu3M2QCJwSFuJ6cDp8s2XmbUXkzbuQhCUzKR86ZH2Vex/VUjb2UaZBGamijA== dependencies: - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-recipes@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.2.tgz#19f47ec25a8788c65d0cc8d217cbebc0bbbb5c63" - integrity sha512-2lcUKMYDiJKvuvRotOxLjH2z9K7jhj8GNUaHxHNkJYbTCUN3LsX1cWrsgeJFDZ/LgI565t3fntpbG9J415ZBXA== +workbox-recipes@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.3.tgz#15beac9d8ae7a3a1c100218094a824b4dd3fd59a" + integrity sha512-IcgiKYmbGiDvvf3PMSEtmwqxwfQ5zwI7OZPio3GWu4PfehA8jI8JHI3KZj+PCfRiUPZhjQHJ3v1HbNs+SiSkig== dependencies: - workbox-cacheable-response "6.5.2" - workbox-core "6.5.2" - workbox-expiration "6.5.2" - workbox-precaching "6.5.2" - workbox-routing "6.5.2" - workbox-strategies "6.5.2" + workbox-cacheable-response "6.5.3" + workbox-core "6.5.3" + workbox-expiration "6.5.3" + workbox-precaching "6.5.3" + workbox-routing "6.5.3" + workbox-strategies "6.5.3" -workbox-routing@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.2.tgz#e0ad46246ba51224fd57eff0dd46891b3220cb9a" - integrity sha512-nR1w5PjF6IVwo0SX3oE88LhmGFmTnqqU7zpGJQQPZiKJfEKgDENQIM9mh3L1ksdFd9Y3CZVkusopHfxQvit/BA== +workbox-routing@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.3.tgz#a0a699d8cc90b5692bd3df24679acbbda3913777" + integrity sha512-DFjxcuRAJjjt4T34RbMm3MCn+xnd36UT/2RfPRfa8VWJGItGJIn7tG+GwVTdHmvE54i/QmVTJepyAGWtoLPTmg== dependencies: - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-strategies@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.2.tgz#56b02e6959c6391351011fc2e5b0829aff1ed859" - integrity sha512-fgbwaUMxbG39BHjJIs2y2X21C0bmf1Oq3vMQxJ1hr6y5JMJIm8rvKCcf1EIdAr+PjKdSk4ddmgyBQ4oO8be4Uw== +workbox-strategies@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.3.tgz#4bea9a48fee16cf43766e0d8138296773c8a9783" + integrity sha512-MgmGRrDVXs7rtSCcetZgkSZyMpRGw8HqL2aguszOc3nUmzGZsT238z/NN9ZouCxSzDu3PQ3ZSKmovAacaIhu1w== dependencies: - workbox-core "6.5.2" + workbox-core "6.5.3" -workbox-streams@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.2.tgz#2fb6ba307f7d2cbda63f64522a197be868b4ea25" - integrity sha512-ovD0P4UrgPtZ2Lfc/8E8teb1RqNOSZr+1ZPqLR6sGRZnKZviqKbQC3zVvvkhmOIwhWbpL7bQlWveLVONHjxd5w== +workbox-streams@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.3.tgz#b6860290031caa7d0e46ad7142315c94359c780b" + integrity sha512-vN4Qi8o+b7zj1FDVNZ+PlmAcy1sBoV7SC956uhqYvZ9Sg1fViSbOpydULOssVJ4tOyKRifH/eoi6h99d+sJ33w== dependencies: - workbox-core "6.5.2" - workbox-routing "6.5.2" + workbox-core "6.5.3" + workbox-routing "6.5.3" -workbox-sw@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.2.tgz#2f5dca0e96c61a450fccf0405095ddf1b6f43bc7" - integrity sha512-2KhlYqtkoqlnPdllj2ujXUKRuEFsRDIp6rdE4l1PsxiFHRAFaRTisRQpGvRem5yxgXEr+fcEKiuZUW2r70KZaw== +workbox-sw@6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.3.tgz#cd2f0c086f4496acd25774ed02c48504189bebdd" + integrity sha512-BQBzm092w+NqdIEF2yhl32dERt9j9MDGUTa2Eaa+o3YKL4Qqw55W9yQC6f44FdAHdAJrJvp0t+HVrfh8AiGj8A== workbox-webpack-plugin@^6.1.5: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.2.tgz#0cf6e1d23d5107a88fd8502fd4f534215e1dd298" - integrity sha512-StrJ7wKp5tZuGVcoKLVjFWlhDy+KT7ZWsKnNcD6F08wA9Cpt6JN+PLIrplcsTHbQpoAV8+xg6RvcG0oc9z+RpQ== + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.3.tgz#c37bb323be4952311565c07db51054fe59c87d73" + integrity sha512-Es8Xr02Gi6Kc3zaUwR691ZLy61hz3vhhs5GztcklQ7kl5k2qAusPh0s6LF3wEtlpfs9ZDErnmy5SErwoll7jBA== dependencies: fast-json-stable-stringify "^2.1.0" pretty-bytes "^5.4.1" upath "^1.2.0" webpack-sources "^1.4.3" - workbox-build "6.5.2" + workbox-build "6.5.3" -workbox-window@6.5.2, workbox-window@^6.1.1: - version "6.5.2" - resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.2.tgz#46d6412cd57039bdf3d5dd914ad21fb3f98fe980" - integrity sha512-2kZH37r9Wx8swjEOL4B8uGM53lakMxsKkQ7mOKzGA/QAn/DQTEZGrdHWtypk2tbhKY5S0jvPS+sYDnb2Z3378A== +workbox-window@6.5.3, workbox-window@^6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.3.tgz#4ade70056cb73477ef1cd8fea7cfd0ecbd825c7f" + integrity sha512-GnJbx1kcKXDtoJBVZs/P7ddP0Yt52NNy4nocjBpYPiRhMqTpJCNrSL+fGHZ/i/oP6p/vhE8II0sA6AZGKGnssw== dependencies: "@types/trusted-types" "^2.0.2" - workbox-core "6.5.2" + workbox-core "6.5.3" worker-farm@^1.7.0: version "1.7.0" @@ -22666,7 +22415,7 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" -worker-loader@^3.0.8: +worker-loader@3.0.8, worker-loader@^3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.8.tgz#5fc5cda4a3d3163d9c274a4e3a811ce8b60dbb37" integrity sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g== @@ -22689,15 +22438,6 @@ wrap-ansi@^3.0.1: string-width "^2.1.1" strip-ansi "^4.0.0" -wrap-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-4.0.0.tgz#b3570d7c70156159a2d42be5cc942e957f7b1131" - integrity sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg== - dependencies: - ansi-styles "^3.2.0" - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -22725,6 +22465,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3" + integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -22797,45 +22546,28 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.2.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" - integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== - dependencies: - async-limiter "~1.0.0" - -ws@^7.2.3, ws@^7.3.1: - version "7.5.7" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" - integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== +ws@^7.3.1: + version "7.5.8" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a" + integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw== -ws@^8.1.0, ws@^8.2.3: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +ws@^8.1.0, ws@^8.2.3, ws@^8.4.2: + version "8.7.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.7.0.tgz#eaf9d874b433aa00c0e0d8752532444875db3957" + integrity sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg== -wslink@^0.1.8: - version "0.1.16" - resolved "https://registry.yarnpkg.com/wslink/-/wslink-0.1.16.tgz#93d28ea342c315b96984e523fae0bf1fdc3b5027" - integrity sha512-SMtnjpo5MGvtBLYlkh/JqTo71qaC0r1VHmqcrFn+wffeQRP0TlBUKM62g4KuNfkKFgE408oBTEOsNa04jVjqYw== - dependencies: - json5 "2.1.0" +x-default-browser@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/x-default-browser/-/x-default-browser-0.4.0.tgz#70cf0da85da7c0ab5cb0f15a897f2322a6bdd481" + integrity sha1-cM8NqF2nwKtcsPFaiX8jIqa91IE= + optionalDependencies: + default-browser-id "^1.0.4" xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== -xhr@^2.0.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" - integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== - dependencies: - global "~4.4.0" - is-function "^1.0.1" - parse-headers "^2.0.0" - xtend "^4.0.0" - xml-js@^1.6.11: version "1.6.11" resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" @@ -22848,33 +22580,26 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml-parse-from-string@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" - integrity sha1-qQKekp09vN7RafPG4oI42VpdWig= - -xml2js@^0.4.5: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - xml@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== +xmlbuilder2@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz#fc499688b35a916f269e7b459c2fa02bb5c0822a" + integrity sha512-h4MUawGY21CTdhV4xm3DG9dgsqyhDkZvVJBx88beqX8wJs3VgyGQgAn5VreHuae6unTQxh115aMK5InCVmOIKw== + dependencies: + "@oozcitak/dom" "1.15.10" + "@oozcitak/infra" "1.0.8" + "@oozcitak/util" "8.3.8" + "@types/node" "*" + js-yaml "3.14.0" xstate@^4.10.0: - version "4.30.6" - resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.30.6.tgz#62b6dea37a500e0e1c0ff7c553a801eea5119554" - integrity sha512-V7liK1cjkZRh6R/MSneG8S5VLGRatpOUcnNieiYJX4LbwKi9eUVUH5V04ugJYVcJ+2oKDKvEFvzk0VnSC7lTag== + version "4.32.1" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.32.1.tgz#1a09c808a66072938861a3b4acc5b38460244b70" + integrity sha512-QYUd+3GkXZ8i6qdixnOn28bL3EvA++LONYL/EMWwKlFSh/hiLndJ8YTnz77FDs+JUXcwU7NZJg7qoezoRHc4GQ== xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" @@ -22927,12 +22652,12 @@ yargs-parser@^15.0.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.7: +yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs@^13.3.0, yargs@^13.3.2: +yargs@^13.3.0: version "13.3.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==