diff --git a/bun.lock b/bun.lock index 8a2bab93..9cedf14d 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,19 @@ "": { "name": "slim", "dependencies": { + "antd": "^4.22.8", + "classnames": "^2.2.6", + "dcmjs": "^0.35.0", + "detect-browser": "^5.2.1", + "dicom-microscopy-viewer": "^0.48.20", + "dicomweb-client": "0.10.3", + "oidc-client": "^1.11.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", + "react-icons": "^3.11.0", + "react-router-dom": "^6.3.0", + "retry": "^0.13.1", }, "devDependencies": { "@babel/preset-env": "^7.15.0", @@ -31,26 +43,14 @@ "@types/retry": "^0.12.1", "@types/uuid": "^8.3.0", "ajv": "6.12.6", - "antd": "^4.22.8", - "classnames": "^2.2.6", "copy-webpack-plugin": "9.1.0", "craco-less": "^2.0.0", - "dcmjs": "^0.35.0", - "detect-browser": "^5.2.1", - "dicom-microscopy-viewer": "^0.48.18", - "dicomweb-client": "0.10.3", "eslint": "^8.57.0", "eslint-plugin-sonarjs": "^0.25.0", "gh-pages": "^5.0.0", "husky": "^9.1.7", - "oidc-client": "^1.11.5", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^3.11.0", - "react-router-dom": "^6.3.0", "react-scripts": "5.0.0", "react-test-renderer": "^18.2.0", - "retry": "^0.13.1", "semantic-release": "21.1.2", "sonarqube-scanner": "^4.3.0", "typescript": "^4.7.4", @@ -404,6 +404,8 @@ "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + "@imagingdatacommons/dicomicc": ["@imagingdatacommons/dicomicc@0.2.3", "", {}, "sha512-gHRHIct+5kyTqONASiuGgjlZVEaawpwqSxfBbltLwhTVYedcA7hs0TPYJk3uR1P5RvvC2fC1lPJ0c8jhkGxtNA=="], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], @@ -816,7 +818,7 @@ "adjust-sourcemap-loader": ["adjust-sourcemap-loader@4.0.0", "", { "dependencies": { "loader-utils": "^2.0.0", "regex-parser": "^2.2.11" } }, "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A=="], - "adm-zip": ["adm-zip@0.5.10", "", {}, "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="], + "adm-zip": ["adm-zip@0.5.12", "", {}, "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ=="], "agent-base": ["agent-base@7.1.1", "", { "dependencies": { "debug": "^4.3.4" } }, "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA=="], @@ -984,7 +986,7 @@ "camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="], - "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], @@ -1112,7 +1114,7 @@ "core-js-compat": ["core-js-compat@3.26.1", "", { "dependencies": { "browserslist": "^4.21.4" } }, "sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A=="], - "core-js-pure": ["core-js-pure@3.32.1", "", {}, "sha512-f52QZwkFVDPf7UEQZGHKx6NYxsxmVGJe5DIvbzOdRMJlmT6yv0KDjR8rmy3ngr/t5wU54c7Sp/qIJH0ppbhVpQ=="], + "core-js-pure": ["core-js-pure@3.26.1", "", {}, "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ=="], "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], @@ -1232,9 +1234,7 @@ "detective": ["detective@5.2.1", "", { "dependencies": { "acorn-node": "^1.8.2", "defined": "^1.0.0", "minimist": "^1.2.6" }, "bin": { "detective": "bin/detective.js" } }, "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw=="], - "dicom-microscopy-viewer": ["dicom-microscopy-viewer@0.48.18", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", "colormap": "^2.3", "dcmjs": "^0.41.0", "dicomicc": "^0.1", "dicomweb-client": "0.10.3", "image-type": "^4.1", "mathjs": "^11.2", "ol": "^10.7.0", "uuid": "^9.0" } }, "sha512-0pNnF6/6yNnE3AbRhN1grQXAcJ3R1i2Af1rYNGn7EL9mzO/0+0vf0dKy695zLheIwVrYRqbSgHeujDrJl33qbw=="], - - "dicomicc": ["dicomicc@0.1.0", "", {}, "sha512-kZejPGjLQ9NsgovSyVsiAuCpq6LofNR9Erc8Tt/vQAYGYCoQnTyWDlg5D0TJJQATKul7cSr9k/q0TF8G9qdDkQ=="], + "dicom-microscopy-viewer": ["dicom-microscopy-viewer@0.48.20", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjph": "^2.4.5", "@imagingdatacommons/dicomicc": "^0.2.3", "colormap": "^2.3", "dcmjs": "^0.41.0", "dicomweb-client": "0.10.3", "image-type": "^4.1", "mathjs": "^11.2", "ol": "^10.7.0", "uuid": "^9.0" } }, "sha512-YMBCThjzKfRTL43hE2XeDvBCegUwL2WRPPeWtkSZDdmOhTFTkNv8eekdL3BHi57po8clALPw/exvSoDpYxd1Mg=="], "dicomweb-client": ["dicomweb-client@0.10.3", "", {}, "sha512-/fHNEAYiz8j+9TNOrNJ0k+hYqirbOT85B7vM7I4VkY8DeDQb4BDUeL3RX6huDVtn6ZQlR91dI+2tejLc5c99wA=="], @@ -3108,6 +3108,8 @@ "@babel/plugin-transform-classes/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + "@babel/runtime-corejs3/core-js-pure": ["core-js-pure@3.32.1", "", {}, "sha512-f52QZwkFVDPf7UEQZGHKx6NYxsxmVGJe5DIvbzOdRMJlmT6yv0KDjR8rmy3ngr/t5wU54c7Sp/qIJH0ppbhVpQ=="], + "@babel/runtime-corejs3/regenerator-runtime": ["regenerator-runtime@0.14.0", "", {}, "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="], "@babel/template/@babel/code-frame": ["@babel/code-frame@7.18.6", "", { "dependencies": { "@babel/highlight": "^7.18.6" } }, "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q=="], @@ -3144,6 +3146,8 @@ "@isaacs/cliui/strip-ansi": ["strip-ansi@7.0.1", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw=="], + "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], @@ -3226,8 +3230,6 @@ "@npmcli/run-script/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], - "@pmmmwh/react-refresh-webpack-plugin/core-js-pure": ["core-js-pure@3.26.1", "", {}, "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ=="], - "@pmmmwh/react-refresh-webpack-plugin/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], "@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="], @@ -3242,8 +3244,6 @@ "@semantic-release/npm/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], - "@svgr/core/camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - "@testing-library/dom/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "@testing-library/dom/pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], @@ -3310,6 +3310,8 @@ "call-bind-apply-helpers/function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "camelcase-keys/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + "camelcase-keys/quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="], "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -3338,6 +3340,8 @@ "data-urls/whatwg-url": ["whatwg-url@8.7.0", "", { "dependencies": { "lodash": "^4.7.0", "tr46": "^2.1.0", "webidl-conversions": "^6.1.0" } }, "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg=="], + "dcmjs/adm-zip": ["adm-zip@0.5.10", "", {}, "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="], + "decamelize-keys/map-obj": ["map-obj@1.0.1", "", {}, "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="], "define-data-property/es-define-property": ["es-define-property@1.0.0", "", { "dependencies": { "get-intrinsic": "^1.2.4" } }, "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ=="], @@ -3614,8 +3618,6 @@ "jest-util/ci-info": ["ci-info@3.7.0", "", {}, "sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog=="], - "jest-validate/camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - "jest-validate/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "jest-validate/jest-get-type": ["jest-get-type@27.5.1", "", {}, "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw=="], @@ -3750,7 +3752,7 @@ "react-dev-utils/loader-utils": ["loader-utils@3.2.1", "", {}, "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw=="], - "react-scripts/camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + "react-icons/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], "react-scripts/eslint": ["eslint@8.30.0", "", { "dependencies": { "@eslint/eslintrc": "^1.4.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.3.0", "espree": "^9.4.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ=="], @@ -3824,8 +3826,6 @@ "sockjs/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "sonarqube-scanner/adm-zip": ["adm-zip@0.5.12", "", {}, "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ=="], - "sonarqube-scanner/commander": ["commander@12.0.0", "", {}, "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA=="], "spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], @@ -4086,6 +4086,8 @@ "detect-port-alt/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "dicom-microscopy-viewer/dcmjs/adm-zip": ["adm-zip@0.5.10", "", {}, "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ=="], + "env-ci/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], "env-ci/execa/human-signals": ["human-signals@4.3.1", "", {}, "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ=="], @@ -4594,8 +4596,6 @@ "env-ci/execa/onetime/mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], - "eslint-plugin-jsx-a11y/aria-query/@babel/runtime-corejs3/core-js-pure": ["core-js-pure@3.26.1", "", {}, "sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ=="], - "eslint-webpack-plugin/schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "jest-circus/jest-matcher-utils/jest-diff/diff-sequences": ["diff-sequences@27.5.1", "", {}, "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ=="], diff --git a/package.json b/package.json index 131e0848..ec4d8ba9 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,21 @@ "last 1 safari version" ] }, + "dependencies": { + "antd": "^4.22.8", + "classnames": "^2.2.6", + "dcmjs": "^0.35.0", + "detect-browser": "^5.2.1", + "dicom-microscopy-viewer": "^0.48.20", + "dicomweb-client": "0.10.3", + "oidc-client": "^1.11.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^3.1.4", + "react-icons": "^3.11.0", + "react-router-dom": "^6.3.0", + "retry": "^0.13.1" + }, "devDependencies": { "ajv": "6.12.6", "@biomejs/biome": "^2.0.0", @@ -60,32 +75,17 @@ "@types/react-router-dom": "^5.3.3", "@types/retry": "^0.12.1", "@types/uuid": "^8.3.0", - "antd": "^4.22.8", - "classnames": "^2.2.6", "copy-webpack-plugin": "9.1.0", "craco-less": "^2.0.0", - "dcmjs": "^0.35.0", - "detect-browser": "^5.2.1", - "dicom-microscopy-viewer": "^0.48.18", - "dicomweb-client": "0.10.3", "eslint": "^8.57.0", "eslint-plugin-sonarjs": "^0.25.0", "gh-pages": "^5.0.0", - "oidc-client": "^1.11.5", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-icons": "^3.11.0", - "react-router-dom": "^6.3.0", + "husky": "^9.1.7", "react-scripts": "5.0.0", "react-test-renderer": "^18.2.0", - "retry": "^0.13.1", "semantic-release": "21.1.2", "sonarqube-scanner": "^4.3.0", - "typescript": "^4.7.4", - "husky": "^9.1.7" - }, - "dependencies": { - "react-error-boundary": "^3.1.4" + "typescript": "^4.7.4" }, "overrides": { "nth-check": "2.0.1", diff --git a/src/components/DicomTagBrowser/DicomTagBrowser.tsx b/src/components/DicomTagBrowser/DicomTagBrowser.tsx index 6dacf66c..02a3056e 100644 --- a/src/components/DicomTagBrowser/DicomTagBrowser.tsx +++ b/src/components/DicomTagBrowser/DicomTagBrowser.tsx @@ -1,9 +1,10 @@ -import { SearchOutlined } from '@ant-design/icons' +import { EyeOutlined, SearchOutlined } from '@ant-design/icons' import { Input, Select, Slider, Table, Typography } from 'antd' import { useEffect, useMemo, useState } from 'react' import type DicomWebManager from '../../DicomWebManager' import './DicomTagBrowser.css' +import { useActiveSeries } from '../../hooks/useActiveSeries' import { useDebounce } from '../../hooks/useDebounce' import { useSlides } from '../../hooks/useSlides' import DicomMetadataStore, { @@ -48,6 +49,7 @@ const DicomTagBrowser = ({ seriesInstanceUID = '', }: DicomTagBrowserProps): JSX.Element => { const { slides, isLoading } = useSlides({ clients, studyInstanceUID }) + const activeSeriesUIDs = useActiveSeries() const [study, setStudy] = useState(undefined) const [displaySets, setDisplaySets] = useState([]) @@ -207,6 +209,7 @@ const DicomTagBrowser = ({ SeriesNumber = '', SeriesDescription = '', Modality = '', + SeriesInstanceUID, } = displaySet const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0] @@ -216,6 +219,7 @@ const DicomTagBrowser = ({ value: index, label: `${SeriesNumber} (${Modality}): ${SeriesDescription}`, description: displayDate, + seriesInstanceUID: SeriesInstanceUID ?? '', } }) }, [sortedDisplaySets]) @@ -452,18 +456,54 @@ const DicomTagBrowser = ({ optionLabelProp="label" optionFilterProp="label" > - {displaySetList.map((item) => ( - - ))} + + ) + })} diff --git a/src/components/SlideViewer.tsx b/src/components/SlideViewer.tsx index 3a5683ae..c99b9241 100644 --- a/src/components/SlideViewer.tsx +++ b/src/components/SlideViewer.tsx @@ -37,6 +37,7 @@ import { import { SettingsRegistration } from '../contexts/SettingsContext' import { runValidations } from '../contexts/ValidationContext' import { StorageClasses } from '../data/uids' +import { ActiveSeriesService } from '../services/ActiveSeriesService' import DicomMetadataStore from '../services/DICOMMetadataStore' import NotificationMiddleware, { NotificationMiddlewareContext, @@ -326,6 +327,37 @@ class SlideViewer extends React.Component { }) } + /** + * Publish active series (image + visible derived data) to ActiveSeriesService + * for use by the DICOM Tag Browser to show eye icons. + */ + private publishActiveSeriesToService = (): void => { + try { + const activeImageSeriesUID = this.props.seriesInstanceUID ?? '' + const derivedSet = new Set() + + this.volumeViewer.getAllAnnotationGroups().forEach((ag) => { + if (this.state.visibleAnnotationGroupUIDs.has(ag.uid)) { + derivedSet.add(ag.seriesInstanceUID) + } + }) + this.volumeViewer.getAllSegments().forEach((segment) => { + if (this.state.visibleSegmentUIDs.has(segment.uid)) { + derivedSet.add(segment.seriesInstanceUID) + } + }) + this.volumeViewer.getAllParameterMappings().forEach((mapping) => { + if (this.state.visibleMappingUIDs.has(mapping.uid)) { + derivedSet.add(mapping.seriesInstanceUID) + } + }) + + ActiveSeriesService.setActiveSeries(activeImageSeriesUID, derivedSet) + } catch { + // volumeViewer may be in a transitional state + } + } + componentDidUpdate( previousProps: SlideViewerProps, _previousState: SlideViewerState, @@ -396,6 +428,8 @@ class SlideViewer extends React.Component { }) this.populateViewports() } + + this.publishActiveSeriesToService() } /** @@ -2133,6 +2167,7 @@ class SlideViewer extends React.Component { } componentWillUnmount = (): void => { + ActiveSeriesService.clear() this.volumeViewer.cleanup() if (this.labelViewer !== null && this.labelViewer !== undefined) { this.labelViewer.cleanup() @@ -2203,6 +2238,7 @@ class SlideViewer extends React.Component { componentDidMount = (): void => { this.componentSetup() this.populateViewports() + this.publishActiveSeriesToService() if (!this.props.slide.areVolumeImagesMonochrome) { let hasICCProfile = false diff --git a/src/hooks/useActiveSeries.ts b/src/hooks/useActiveSeries.ts new file mode 100644 index 00000000..4be222c1 --- /dev/null +++ b/src/hooks/useActiveSeries.ts @@ -0,0 +1,22 @@ +import { useEffect, useState } from 'react' + +import { ActiveSeriesService } from '../services/ActiveSeriesService' + +/** + * Subscribe to active series (image + visible derived data) for the DICOM Tag Browser. + * Returns the set of SeriesInstanceUIDs that are currently active in the viewport. + */ +export function useActiveSeries(): Set { + const [activeSeriesUIDs, setActiveSeriesUIDs] = useState>(() => + ActiveSeriesService.getActiveSeriesUIDs(), + ) + + useEffect(() => { + const unsubscribe = ActiveSeriesService.subscribe(() => { + setActiveSeriesUIDs(ActiveSeriesService.getActiveSeriesUIDs()) + }) + return unsubscribe + }, []) + + return activeSeriesUIDs +} diff --git a/src/services/ActiveSeriesService.ts b/src/services/ActiveSeriesService.ts new file mode 100644 index 00000000..607ce76b --- /dev/null +++ b/src/services/ActiveSeriesService.ts @@ -0,0 +1,94 @@ +/** + * Centralized service for tracking which series are currently "active" in the + * viewport - i.e., the active image and any visible derived data (annotations, + * segmentations, parametric maps, etc.). Used by the DICOM Tag Browser to show + * an eye icon next to active series. + */ + +export interface ActiveSeriesState { + /** The primary image series displayed in the viewport (from URL) */ + activeImageSeriesUID: string + /** Derived series with visible overlays (annotations, segmentations, etc.) */ + activeDerivedSeriesUIDs: Set +} + +type Listener = (state: ActiveSeriesState) => void + +class ActiveSeriesServiceImpl { + private activeImageSeriesUID = '' + private activeDerivedSeriesUIDs = new Set() + private listeners: Listener[] = [] + + /** Get all series UIDs that are currently active (image + derived) */ + getActiveSeriesUIDs(): Set { + const result = new Set(this.activeDerivedSeriesUIDs) + if (this.activeImageSeriesUID) { + result.add(this.activeImageSeriesUID) + } + return result + } + + /** Get the current state */ + getState(): ActiveSeriesState { + return { + activeImageSeriesUID: this.activeImageSeriesUID, + activeDerivedSeriesUIDs: new Set(this.activeDerivedSeriesUIDs), + } + } + + /** Check if a series is active (image or has visible derived data) */ + isSeriesActive(seriesInstanceUID: string): boolean { + return ( + seriesInstanceUID === this.activeImageSeriesUID || + this.activeDerivedSeriesUIDs.has(seriesInstanceUID) + ) + } + + /** Update the active series state. Called by SlideViewer and route-aware components. */ + setActiveSeries( + activeImageSeriesUID: string, + activeDerivedSeriesUIDs: Iterable, + ): void { + const derivedSet = new Set(activeDerivedSeriesUIDs) + const imageChanged = this.activeImageSeriesUID !== activeImageSeriesUID + const derivedChanged = + derivedSet.size !== this.activeDerivedSeriesUIDs.size || + [...derivedSet].some((uid) => !this.activeDerivedSeriesUIDs.has(uid)) + + if (!imageChanged && !derivedChanged) { + return + } + + this.activeImageSeriesUID = activeImageSeriesUID + this.activeDerivedSeriesUIDs = derivedSet + this._notify() + } + + /** Clear all active series (e.g. when navigating away from the viewer). */ + clear(): void { + this.activeImageSeriesUID = '' + this.activeDerivedSeriesUIDs = new Set() + this._notify() + } + + /** Subscribe to active series changes. Returns unsubscribe function. */ + subscribe(listener: Listener): () => void { + this.listeners.push(listener) + return () => { + this.listeners = this.listeners.filter((l) => l !== listener) + } + } + + private _notify(): void { + const state = this.getState() + this.listeners.forEach((listener) => { + try { + listener(state) + } catch (err) { + console.error('[ActiveSeriesService] Listener error:', err) + } + }) + } +} + +export const ActiveSeriesService = new ActiveSeriesServiceImpl()