From bb5d2c0c5d279360dcd0508ffc799ecdd4901608 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Wed, 25 Mar 2026 03:02:16 -0700 Subject: [PATCH 01/34] Upgrade axios version --- package-lock.json | 229 +++++++++--------------------------- package.json | 4 +- src/services/httpService.js | 12 +- 3 files changed, 64 insertions(+), 181 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5d0b2107..f226f9d78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@dnd-kit/sortable": "^10.0.0-next-202410244445", "@dnd-kit/utilities": "^3.2.2", "aimapi": "^0.1.26", - "axios": "^0.21.4", + "axios": "^1.13.6", "bootstrap": "^5.1.3", "cornerstone-core": "^2.6.1", "cornerstone-math": "^0.1.8", @@ -2892,7 +2892,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.3.0" @@ -2908,7 +2907,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2921,7 +2919,6 @@ "version": "4.11.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", - "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2931,7 +2928,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2955,7 +2951,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -2973,7 +2968,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.20.2" @@ -2989,14 +2983,12 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -3009,7 +3001,6 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3020,7 +3011,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "deprecated": "Use @eslint/config-array instead", - "dev": true, "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", @@ -3035,7 +3025,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -3053,14 +3042,12 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, "license": "MIT" }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -3075,7 +3062,6 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@isaacs/cliui": { @@ -3967,7 +3953,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4029,7 +4014,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4043,7 +4027,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4053,7 +4036,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -4754,7 +4736,6 @@ "version": "8.56.11", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz", "integrity": "sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", @@ -4765,7 +4746,6 @@ "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, "license": "MIT", "dependencies": { "@types/eslint": "*", @@ -4776,7 +4756,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -4923,7 +4902,6 @@ "version": "22.2.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz", "integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.13.0" @@ -5517,14 +5495,12 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true, "license": "ISC" }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", @@ -5535,28 +5511,24 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", @@ -5568,14 +5540,12 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -5588,7 +5558,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" @@ -5598,7 +5567,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" @@ -5608,14 +5576,12 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -5632,7 +5598,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -5646,7 +5611,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -5659,7 +5623,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -5674,7 +5637,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", @@ -5685,14 +5647,12 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, "license": "Apache-2.0" }, "node_modules/abab": { @@ -5733,7 +5693,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5770,7 +5729,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -5783,7 +5741,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5913,7 +5870,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -5931,7 +5887,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5948,7 +5903,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, "license": "MIT" }, "node_modules/ajv-keywords": { @@ -6067,7 +6021,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -6342,7 +6295,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -6438,12 +6390,30 @@ } }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, "node_modules/axobject-query": { @@ -7162,7 +7132,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -7358,7 +7327,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -7596,7 +7564,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, "node_modules/buffer-xor": { @@ -7921,7 +7888,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0" @@ -7951,6 +7917,16 @@ "node": ">=10" } }, + "node_modules/chromedriver/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -8192,7 +8168,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -8494,7 +8469,6 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -9312,7 +9286,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -9419,7 +9392,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -9577,7 +9549,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" @@ -9873,7 +9844,6 @@ "version": "5.18.3", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -10052,7 +10022,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -10068,15 +10037,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -10171,7 +10140,6 @@ "version": "8.57.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -10696,7 +10664,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10706,7 +10673,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -10722,7 +10688,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -10739,7 +10704,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -10752,14 +10716,12 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/eslint/node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -10777,7 +10739,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10790,7 +10751,6 @@ "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -10807,7 +10767,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -10820,7 +10779,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -10830,7 +10788,6 @@ "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.20.2" @@ -10846,14 +10803,12 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, "license": "MIT" }, "node_modules/eslint/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10866,7 +10821,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -10879,7 +10833,6 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -10892,7 +10845,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", @@ -10910,7 +10862,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -10937,7 +10888,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -10950,7 +10900,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -10997,7 +10946,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -11274,7 +11222,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, "license": "MIT" }, "node_modules/fast-memoize": { @@ -11287,14 +11234,12 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "dev": true, "license": "MIT" }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -11337,7 +11282,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" @@ -11488,7 +11432,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -11505,7 +11448,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -11520,13 +11462,12 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -11872,7 +11813,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -12093,7 +12033,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -12114,7 +12053,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -12127,7 +12065,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/global-modules": { @@ -12261,14 +12198,12 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, "license": "MIT" }, "node_modules/gzip-size": { @@ -12337,7 +12272,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12936,7 +12870,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -13023,7 +12956,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -13044,7 +12976,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -13297,7 +13228,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13355,7 +13285,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -13463,7 +13392,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13710,7 +13638,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -15935,7 +15862,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -15950,7 +15876,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -16000,7 +15925,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -16072,7 +15996,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -16098,7 +16021,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, "license": "MIT" }, "node_modules/json5": { @@ -16241,7 +16163,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -16322,7 +16243,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -16361,7 +16281,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" @@ -16385,7 +16304,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -16432,7 +16350,6 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, "license": "MIT" }, "node_modules/lodash.sortby": { @@ -16645,7 +16562,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, "license": "MIT" }, "node_modules/merge2": { @@ -16718,7 +16634,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -16728,7 +16643,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -16851,7 +16765,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -16989,7 +16902,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, "license": "MIT" }, "node_modules/natural-compare-lite": { @@ -17023,7 +16935,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, "license": "MIT" }, "node_modules/no-case": { @@ -17368,7 +17279,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -17412,7 +17322,6 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -17454,7 +17363,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -17470,7 +17378,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -17645,7 +17552,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -17655,7 +17561,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17665,7 +17570,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -19361,7 +19265,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -19527,7 +19430,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, "license": "MIT" }, "node_modules/psl": { @@ -19632,7 +19534,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -20907,7 +20808,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -21085,7 +20985,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -21097,7 +20996,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -21254,7 +21152,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -21543,7 +21440,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -21704,7 +21600,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -21717,7 +21612,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -21864,7 +21758,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -22520,7 +22413,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -22987,7 +22879,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -23121,7 +23012,6 @@ "version": "5.31.6", "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -23140,7 +23030,6 @@ "version": "5.3.14", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -23175,7 +23064,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -23192,7 +23080,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" @@ -23205,14 +23092,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, "license": "MIT" }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -23232,7 +23117,6 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, "license": "MIT" }, "node_modules/test-exclude": { @@ -23254,7 +23138,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, "license": "MIT" }, "node_modules/thenify": { @@ -23509,7 +23392,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -23640,6 +23522,21 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -23682,7 +23579,6 @@ "version": "6.13.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -24032,7 +23928,6 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -24066,7 +23961,6 @@ "version": "5.102.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -24366,7 +24260,6 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" @@ -24376,7 +24269,6 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -24393,7 +24285,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" @@ -24406,14 +24297,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, "license": "MIT" }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -24516,7 +24405,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -24623,7 +24511,6 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -25128,7 +25015,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -25272,7 +25158,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 7297f6601..920679cd6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@dnd-kit/sortable": "^10.0.0-next-202410244445", "@dnd-kit/utilities": "^3.2.2", "aimapi": "^0.1.26", - "axios": "^0.21.4", + "axios": "^1.13.6", "bootstrap": "^5.1.3", "cornerstone-core": "^2.6.1", "cornerstone-math": "^0.1.8", @@ -108,4 +108,4 @@ "image-size": "1.2.1", "brace-expansion": "2.0.1" } -} \ No newline at end of file +} diff --git a/src/services/httpService.js b/src/services/httpService.js index 021a6cb40..cdce3ada5 100644 --- a/src/services/httpService.js +++ b/src/services/httpService.js @@ -3,8 +3,6 @@ import axios from "axios"; import auth from "./authService"; // axios.defaults.withCredentials = false; -axios.defaults.headers.common["Content-Type"] = - "application/json, multipart/form-data"; axios.interceptors.request.use( async (config) => { @@ -12,7 +10,7 @@ axios.interceptors.request.use( const apikey = sessionStorage.getItem("API_KEY"); const user = sessionStorage.getItem("username"); if (apikey && user) { - config.params = {}; + config.params = { ...config.params }; config.params["user"] = user; } const header = await auth.getAuthHeader(); @@ -60,10 +58,10 @@ function mode() { } export default { - get: axios.get, - post: axios.post, - put: axios.put, - delete: axios.delete, + get: axios.get.bind(axios), + post: axios.post.bind(axios), + put: axios.put.bind(axios), + delete: axios.delete.bind(axios), apiUrl, wadoUrl, mode, From c23c274860862723ca6e952cab02a790cfb4a9db Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Wed, 25 Mar 2026 12:24:37 -0700 Subject: [PATCH 02/34] Upgrade npm package --- CLAUDE.md | 83 +++++ package-lock.json | 768 ++++++++++++++++++++++------------------------ 2 files changed, 457 insertions(+), 394 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..c810a1224 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,83 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +ePAD JS is a React-based DICOM medical imaging viewer and annotation platform. It enables radiologists to view medical images from PACS systems, create/manage annotations (AIMs), and generate clinical reports. + +## Commands + +```bash +npm start # Development server +npm run build # Production build +npm test # Run tests (Jest + jsdom) +``` + +No lint script is defined. Tests use `react-app-rewired test --env=jsdom`. + +To run a single test file: +```bash +npm test -- --testPathPattern= +``` + +## Architecture + +### Tech Stack +- **React 16** (class components predominate, some hooks) +- **Redux + redux-thunk** for state management +- **React Router v4** for routing +- **Cornerstone.js** for DICOM image rendering +- **Keycloak** (v4.0.0-beta.2) for OAuth/OIDC authentication +- **Axios** for HTTP, with auth interceptors in `src/services/httpService.js` + +### Routing (`src/App.js`) +All routes except `/login`/`/logout` are wrapped in `ProtectedRoute`: +- `/display` — DICOM viewer (main imaging workflow) +- `/list/:pid` — Patient/study search (SearchView) +- `/flex/:pid` — Flexible layout view +- `/anotate` — Annotation creation +- `/progress` — Upload progress tracking +- `/management` — Admin panel +- `/annotationSearch` — Cross-project annotation search + +### Redux Store (`src/reducers.js`) +Three combined reducers: +- **`annotationsListReducer`** — Core viewer state: `openSeries[]`, `aimsList{}`, selected project/patients/studies, `projectMap{}`, AIM templates, UI toggles (`showAnnotations`, `showLabels`, etc.) +- **`searchViewReducer`** — Study list state, user-selected studies, Cornerstone instances +- **`managementReducer`** — Admin/management UI state + +### Service Layer (`src/services/`) +19 service modules wrapping the backend API. Key ones: +- `httpService.js` — Axios instance with auth header injection; reads `apiUrl`/`wadoUrl` from `sessionStorage` +- `annotationServices.js` — AIM CRUD +- `seriesServices.js` / `studyServices.js` — DICOM data fetching +- `authService.js` — Keycloak integration +- `projectServices.js` — Project management + +### Key Component Modules (`src/components/`) +- **`display/`** — DICOM viewer; `displayView.jsx` is the largest file (~116KB). Contains Cornerstone tool integration (Arrow, Circle, Line, Bidirectional, Freehand, Probe, segmentation) +- **`searchView/`** — Patient/study search with reporting (ADLA, RECIST, Waterfall), filters, and a drag-drop study grid +- **`annotationsList/`** — Annotation management with Redux reducer/actions; includes `annotationDock/` +- **`sideBar/`** — Left navigation: patient/project tree, worklist integration +- **`ToolMenu/`** — Drawing/measurement tool palette including SmartBrush for segmentation +- **`management/`** — Project/user/permission administration +- **`aimEditor/`** — AIM annotation editor with tag editing (`tagEditor/`) +- **`MediaExport/`** — GIF and PowerPoint export +- **`common/`** — Shared UI: `protectedRoute.jsx`, modals, `SelectModalMenu.jsx` + +### Session & Persistence +- **`sessionStorage`**: API credentials (`apiUrl`, `wadoUrl`, `username`, tokens), operating mode (`lite` vs standard) +- **`localStorage`**: Tree data, PHI visibility preferences, filters + +### Build Configuration +- `react-app-rewired` overrides CRA config via `config-overrides.js` — adds Node.js polyfills (`NodePolyfillPlugin`) for crypto/Buffer, TypeScript resolution, and `fs: false` fallback +- `jsconfig.json` sets `baseUrl: "src"` so imports can be relative to `src/` (e.g., `import X from 'components/...'`) +- Babel: `@babel/preset-env`, `@babel/react`, Emotion plugin + +### Deployment +Docker multi-stage build: Node 11 Alpine for build → Nginx for serving. Nginx proxies: +- `/api` → `epad_lite:8080` (backend) +- `/pacs` → `epad_dicomweb:8090` (DICOM server) +- `/keycloak` → Keycloak auth server +- WADO images cached for 60 minutes diff --git a/package-lock.json b/package-lock.json index f226f9d78..3dac4f8e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3212,9 +3212,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -4106,9 +4106,9 @@ } }, "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -4622,16 +4622,6 @@ "node": ">= 6" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -5851,9 +5841,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5884,9 +5874,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -6249,9 +6239,9 @@ } }, "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "license": "MIT" }, "node_modules/assert": { @@ -7217,46 +7207,36 @@ "license": "MIT" }, "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/body-parser/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7271,13 +7251,13 @@ } }, "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -7396,34 +7376,37 @@ } }, "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", "license": "MIT", "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, "node_modules/browserify-sign": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", - "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", "license": "ISC", "dependencies": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.5", - "hash-base": "~3.0", + "elliptic": "^6.6.1", "inherits": "^2.0.4", - "parse-asn1": "^5.1.7", + "parse-asn1": "^5.1.9", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" }, "engines": { - "node": ">= 0.12" + "node": ">= 0.10" } }, "node_modules/browserify-sign/node_modules/isarray": { @@ -7592,9 +7575,9 @@ "license": "MIT" }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", "engines": { @@ -8217,30 +8200,33 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, "node_modules/confusing-browser-globals": { "version": "1.0.11", @@ -8300,9 +8286,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -8433,9 +8419,9 @@ } }, "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "license": "MIT" }, "node_modules/create-hash": { @@ -8480,25 +8466,29 @@ } }, "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", "license": "MIT", "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" }, "engines": { - "node": "*" + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/crypto-random-string": { @@ -8673,9 +8663,9 @@ } }, "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -9507,9 +9497,9 @@ } }, "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "license": "MIT" }, "node_modules/dir-glob": { @@ -9786,9 +9776,9 @@ } }, "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "license": "MIT" }, "node_modules/emittery": { @@ -9821,9 +9811,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", "engines": { @@ -10573,9 +10563,9 @@ } }, "node_modules/eslint-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -11065,63 +11055,67 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true, "license": "MIT" }, "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -11341,9 +11335,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -11386,18 +11380,18 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -11459,9 +11453,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "license": "ISC" }, "node_modules/follow-redirects": { @@ -11720,15 +11714,17 @@ } }, "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -12622,20 +12618,24 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-parser-js": { @@ -12701,9 +12701,9 @@ "license": "MIT" }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -15922,9 +15922,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -16049,21 +16049,21 @@ } }, "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.3.0.tgz", + "integrity": "sha512-0kjkYHJBkAy50Z5QzArZ7udmvxrJzkpKYW27fiF//BrMY7TQibYLl+FYIXN2BiYmwMIVzSfD8aDRj6IzgBX2/w==", "dev": true, "license": "MIT", "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" + "esprima": "1.2.5", + "static-eval": "2.1.1", + "underscore": "1.13.6" } }, "node_modules/jsonpath/node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==", "dev": true, "bin": { "esparse": "bin/esparse.js", @@ -16316,15 +16316,15 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, "node_modules/lodash.clonedeep": { @@ -16543,11 +16543,14 @@ "license": "MIT" }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-source-map": { "version": "1.1.0", @@ -16585,9 +16588,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -16612,9 +16615,9 @@ } }, "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "license": "MIT" }, "node_modules/mime": { @@ -16693,9 +16696,9 @@ } }, "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -16762,9 +16765,9 @@ "license": "MIT" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -16880,9 +16883,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -16956,9 +16959,9 @@ "license": "0BSD" }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -17102,9 +17105,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -17266,9 +17269,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { @@ -17473,16 +17476,15 @@ } }, "node_modules/parse-asn1": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", - "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", "license": "ISC", "dependencies": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", "evp_bytestokey": "^1.0.3", - "hash-base": "~3.0", - "pbkdf2": "^3.1.2", + "pbkdf2": "^3.1.5", "safe-buffer": "^5.2.1" }, "engines": { @@ -17616,9 +17618,9 @@ } }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "license": "MIT", "dependencies": { "isarray": "0.0.1" @@ -19189,19 +19191,29 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/postcss-svgo/node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/postcss-svgo/node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.2.tgz", + "integrity": "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==", "dev": true, "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^4.1.3", "css-tree": "^1.1.3", "csso": "^4.2.0", "picocolors": "^1.0.0", + "sax": "^1.5.0", "stable": "^0.1.8" }, "bin": { @@ -19454,9 +19466,9 @@ } }, "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "license": "MIT" }, "node_modules/pump": { @@ -19492,12 +19504,12 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -19609,31 +19621,21 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -21078,9 +21080,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", "bin": { @@ -21405,25 +21407,25 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -21440,6 +21442,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -21515,16 +21518,16 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -21628,15 +21631,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -21923,96 +21980,19 @@ "license": "MIT" }, "node_modules/static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escodegen": "^1.8.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", "dev": true, "license": "MIT", "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" + "escodegen": "^2.1.0" } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -22575,9 +22555,10 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -22596,13 +22577,13 @@ } }, "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -22781,9 +22762,9 @@ } }, "node_modules/svgo/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -23027,15 +23008,14 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -23061,9 +23041,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -23202,9 +23182,9 @@ "license": "MIT" }, "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, "license": "MIT", "engines": { @@ -23569,9 +23549,9 @@ } }, "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true, "license": "MIT" }, @@ -24030,9 +24010,9 @@ } }, "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -24147,9 +24127,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -24266,9 +24246,9 @@ } }, "node_modules/webpack/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -24605,9 +24585,9 @@ } }, "node_modules/workbox-build/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { From 9b8bc80ad36e566aaec699225d3e153a20be95f3 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Thu, 16 Apr 2026 22:42:32 -0700 Subject: [PATCH 03/34] Refactor series openning --- .../annotationSearch/AnnotationTable.jsx | 38 ++--- .../annotationDock/newAnnotationsLink.jsx | 33 ++--- .../annotationsList/containers/listItem.jsx | 136 ++++-------------- .../annotationsList/selectSerieModal.jsx | 46 +++--- src/components/common/openSeriesHelper.js | 96 +++++++++++++ src/components/flexView/index.jsx | 41 ++---- src/components/searchView/Annotations.jsx | 57 ++------ src/components/searchView/Report.jsx | 16 +-- src/components/searchView/Series.jsx | 79 ++-------- src/components/searchView/Studies.jsx | 67 ++------- src/components/sideBar/sideBarWorklist.jsx | 59 +++----- 11 files changed, 242 insertions(+), 426 deletions(-) create mode 100644 src/components/common/openSeriesHelper.js diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index 354e667e3..ee3a7d9c1 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -13,8 +13,6 @@ import { clearCarets, convertDateFormat } from "../../Utils/aid.js"; import { changeActivePort, jumpToAim, - addToGrid, - getSingleSerie, startLoading, loadCompleted, annotationsLoadingError, @@ -22,6 +20,7 @@ import { setSeriesData, storeAimSelection } from "../annotationsList/action"; +import { openSeriesInDisplay } from "../common/openSeriesHelper"; import { formatDate } from "../flexView/helperMethods"; import { getSeries, getSignificantSeries } from "../../services/seriesServices"; import SelectSerieModal from "../annotationsList/selectSerieModal"; @@ -559,34 +558,17 @@ function AnnotationTable(props) { } setSelected(seriesArr); - if (props.openSeries.length === maxPort) { - setShowSelectSeriesModal(true); - return; - } - //get extraction of the series (extract unopen series) - if (seriesArr && seriesArr.length > 0) seriesArr = excludeOpenSeries(seriesArr); - - // filter the series according to displayable modalities seriesArr = Array.isArray(seriesArr) ? seriesArr.filter(isSupportedModality) : []; - //check if there is enough room - if (seriesArr.length + props.openSeries.length > maxPort) { - //if there is not bring the modal - setShowSelectSeriesModal(true); - // TODO show toast - } else { - //if there is enough room - //add serie to the grid - const promiseArr = []; - - existingData = getExistingData(selected); - for (let i = 0; i < seriesArr.length; i++) { - props.dispatch(addToGrid(seriesArr[i], aimID)); - promiseArr.push(props.dispatch(getSingleSerie(seriesArr[i], aimID, null, existingData))); - } - //getsingleSerie - Promise.all(promiseArr).then(() => { props.switchToDisplay(); }).catch((err) => console.error(err)); - } + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: props.switchToDisplay, + openSeries: props.openSeries, + series: seriesArr, + aimID, + existingData: getExistingData(selected), + onGridFull: () => setShowSelectSeriesModal(true), + }); }; diff --git a/src/components/annotationsList/annotationDock/newAnnotationsLink.jsx b/src/components/annotationsList/annotationDock/newAnnotationsLink.jsx index 5df38288b..41812b0fb 100644 --- a/src/components/annotationsList/annotationDock/newAnnotationsLink.jsx +++ b/src/components/annotationsList/annotationDock/newAnnotationsLink.jsx @@ -1,12 +1,12 @@ import React, { useEffect, useState } from "react"; import { connect } from "react-redux"; import { - getSingleSerie, clearSelection, changeActivePort, addToGrid, jumpToAim, } from "../action"; +import { openSeriesInDisplay } from "../../common/openSeriesHelper"; import "../annotationsList.css"; import cornerstone from "cornerstone-core"; @@ -90,40 +90,33 @@ const annotationsLink = (props) => { } const displayAnnotations = (e, selected) => { - const { openSeriesAddition, activePort } = props; - const maxPort = parseInt(sessionStorage.getItem("maxPort")); - - let isGridFull = openSeries.length === maxPort; const { isOpen, index } = checkIfSerieOpen(selected.seriesUID, selected.imgIDs); if (isOpen) { + // Fusion-aware jump: skip jump when the active layer is the fused CT companion. const imageUID = Object.keys(selected.imgIDs); const imgIDArr = imageUID[0].split("/frames/"); props.dispatch(changeActivePort(index)); - // if ct do not jump to aim - const {seriesUID} = getFusedSerieInfoAndAnnotations({...props, activePort: index}); + const { seriesUID } = getFusedSerieInfoAndAnnotations({ ...props, activePort: index }); const notFusionCT = seriesUID === props.openSeries[index].seriesUID; if (notFusionCT) { - // No need to change props.dispatch(jumpToAim(selected.seriesUID, selected.aimID, index)); - // change the arguments to handle the multiframe handleJumpToAim(selected.aimID, index, imgIDArr[0], imgIDArr[1]); } else { console.log('Cannot jump on a fused image that is not the active layer'); } props.dispatch(clearSelection()); } else { - if (isGridFull) { - props.dispatch(addToGrid(selected, selected.aimID, props.activePort)); - } else { - props.dispatch(addToGrid(selected, selected.aimID)); - } - const list = getExistingSeriesData(selected); - props - .dispatch(getSingleSerie(selected, selected.aimID, null, list)) - .then(() => {}) - .catch((err) => console.error(err)); - props.dispatch(clearSelection()); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => {}, // already in display view + openSeries: props.openSeries, + series: selected, + aimID: selected.aimID, + existingData: getExistingSeriesData(selected), + // When grid is full, replace the active viewport instead of blocking. + onGridFull: () => props.dispatch(addToGrid(selected, selected.aimID, props.activePort)), + }); } }; diff --git a/src/components/annotationsList/containers/listItem.jsx b/src/components/annotationsList/containers/listItem.jsx index a832b3575..4ef09d202 100644 --- a/src/components/annotationsList/containers/listItem.jsx +++ b/src/components/annotationsList/containers/listItem.jsx @@ -7,13 +7,13 @@ import { toggleAllAnnotations, changeActivePort, showAnnotationWindow, - getSingleSerie, alertViewPortFull, addToGrid, updatePatient, jumpToAim, showAnnotationDock } from "../action"; +import { openSeriesInDisplay } from "../../common/openSeriesHelper"; const maxPort = parseInt(sessionStorage.getItem("maxPort")); @@ -62,134 +62,56 @@ class ListItem extends React.Component { }; openSerie = async e => { - const { patientID, studyUID, seriesUID } = this.props.serie; - const openSeries = Object.values(this.props.openSeries); - let serieCheck = this.checkIfSerieOpen(this.props.serie); - //check if there is enough space in the grid - let isGridFull = openSeries.length === maxPort; - //check if the serie is already open - if (serieCheck.isOpen) { - this.props.dispatch(changeActivePort(serieCheck.index)); - } - if (!serieCheck.isOpen) { - if (isGridFull) { - this.props.dispatch(alertViewPortFull()); - } else { - this.props.dispatch(addToGrid(this.props.serie)); - this.props - .dispatch(getSingleSerie(this.props.serie)) - .then(() => {}) - .catch(err => console.log(err)); - // -----> Delete after v1.0 <----- - // this.props.dispatch( - // updatePatient("serie", true, patientID, studyUID, seriesUID) - // ); - } - } + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => {}, // already in display view + openSeries: Object.values(this.props.openSeries), + series: this.props.serie, + }); }; handleAnnotationClick = async e => { const { seriesUID, studyUID, patientID } = this.props.serie; const { seriesid, aimid } = e.target.dataset; - const activeSeriesUID = this.props.openSeries[this.props.activePort] - .seriesUID; + const activeSeriesUID = this.props.openSeries[this.props.activePort].seriesUID; + if (activeSeriesUID === seriesid) { - // this.checkIfSerieOpen(seriesid).index; this.props.dispatch(jumpToAim(seriesid, aimid, this.props.activePort)); } else { - //if doesn't match check if the serie exists in the open series - const isOpen = this.checkIfSerieOpen(seriesid).isOpen; - const index = this.checkIfSerieOpen(seriesid).index; - if (isOpen) { - // if it exists in the openSeries update activeport - this.props.dispatch(changeActivePort(index)); - //update the status of the clicked annotation - this.props.dispatch(jumpToAim(seriesid, aimid, index)); - } else { - //else get single serie dispatch action - if (this.props.openSeries.length === maxPort) { - this.props.dispatch(alertViewPortFull()); - } else { - // let { patientID, studyUID, seriesUID, projectID } = serie; - this.props.dispatch(addToGrid(this.props.serie, aimid)); - this.props - .dispatch(getSingleSerie(this.props.serie, aimid)) - .then(() => { - // this.props.dispatch(showAnnotationDock()); - - this.props.dispatch( - updateAnnotationDisplay( - patientID, - studyUID, - seriesUID, - aimid, - true - ) - ); - }) - .catch(err => console.log(err)); - } - } + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => {}, // already in display view + openSeries: this.props.openSeries, + series: this.props.serie, + aimID: aimid, + })?.then(() => { + this.props.dispatch( + updateAnnotationDisplay(patientID, studyUID, seriesUID, aimid, true) + ); + }); } this.props.dispatch(showAnnotationWindow()); }; handleToggleSerie = async (checked, e, id) => { - //select de select all anotations - const { patientID, studyUID, seriesUID } = this.props.serie; - // const { seriesid } = e.target.dataset; - // const activeSeriesUID = this.props.openSeries[this.props.activePort] - // .seriesUID; - //check if user toggle on or off and change the state accordingly + const { seriesUID } = this.props.serie; await this.setState({ displayAnnotations: checked }); - // if checked true - const isOpen = this.checkIfSerieOpen(seriesUID).isOpen; - const index = this.checkIfSerieOpen(seriesUID).index; + const { isOpen, index } = this.checkIfSerieOpen(seriesUID); if (checked) { - //check if the serie is already open - //if open if (isOpen) { - //update the active port this.props.dispatch(changeActivePort(index)); - // change the annotations as displayed in patient and aimlist - // -----> Delete after v1.0 <----- - // this.props.dispatch( - // updatePatient("serie", checked, patientID, studyUID, seriesUID) - // ); this.props.dispatch(toggleAllAnnotations(seriesUID, checked)); - //else - if not open } else { - //check if the grid is full - if (this.props.openSeries.length === maxPort) { - //if full bring modal - this.props.dispatch(alertViewPortFull()); - //else - not full - } else { - //addtogrid - this.props.dispatch(addToGrid(this.props.serie)); - //getsingleserie - this.props - .dispatch(getSingleSerie(this.props.serie)) - .then(() => {}) - .catch(err => console.log(err)); - //update patient?? with serie - // -----> Delete after v1.0 <----- - // this.props.dispatch( - // updatePatient("serie", checked, patientID, studyUID, seriesUID) - // ); - } + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => {}, // already in display view + openSeries: this.props.openSeries, + series: this.props.serie, + }); } - // if checked false } else { - //update patients and aimlist just annotations to be false - // -----> Delete after v1.0 <----- - // this.props.dispatch( - // updatePatient("serie", checked, patientID, studyUID, seriesUID) - // ); this.props.dispatch(toggleAllAnnotations(seriesUID, checked)); - if (isOpen) { - this.props.dispatch(changeActivePort(index)); - } + if (isOpen) this.props.dispatch(changeActivePort(index)); } }; diff --git a/src/components/annotationsList/selectSerieModal.jsx b/src/components/annotationsList/selectSerieModal.jsx index 7c9e5db7c..b85211581 100644 --- a/src/components/annotationsList/selectSerieModal.jsx +++ b/src/components/annotationsList/selectSerieModal.jsx @@ -9,11 +9,10 @@ import Button from "react-bootstrap/Button"; import { clearGrid, getWholeData, - getSingleSerie, clearSelection, - addToGrid, setSeriesData, } from "./action"; +import { openSeriesInDisplay } from "../common/openSeriesHelper"; import SelectionItem from "./containers/selectionItem"; import { FaRegCheckSquare } from "react-icons/fa"; import { getSeries, setSignificantSeries } from "../../services/seriesServices"; @@ -217,28 +216,29 @@ class selectSerieModal extends React.Component { displaySelection = async (aimID) => { let studies = Object.values(this.props.seriesPassed); let series = []; - // TODO: what is the logic here? - studies.forEach((arr) => { - series = series.concat(arr); - }); + studies.forEach((arr) => { series = series.concat(arr); }); const seriesArr = await this.saveSignificantSeries(series); - //concatanete all arrays to getther - // for (let key of Object.keys(selectedToDisplay)) { - for (let el of seriesArr) { - let serie = this.findSerieFromSeries(el.seriesUID, series); - const existingData = this.getExistingSeriesData(serie); - if (aimID) this.props.dispatch(addToGrid(serie, aimID)); - else this.props.dispatch(addToGrid(serie, serie.aimID, null, this.props.worklistID )); - if (this.state.selectionType === "aim") { - this.props.dispatch(getSingleSerie(serie, serie.aimID, this.wadoUrl, existingData)); - } else { - if (aimID) - this.props.dispatch(getSingleSerie(serie, aimID, this.wadoUrl, existingData)); - else this.props.dispatch(getSingleSerie(serie, null, this.wadoUrl, existingData)); - } - } - this.props.history.push("/display"); - this.handleCancel(true); + + // Resolve each series and embed the effective aimID so the helper can use it. + const resolvedSeries = seriesArr.map(el => { + const serie = this.findSerieFromSeries(el.seriesUID, series); + return { + ...serie, + aimID: this.state.selectionType === "aim" ? serie.aimID : (aimID || null), + }; + }); + + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => { + this.props.history.push("/display"); + this.handleCancel(true); + }, + openSeries: this.props.openSeries, + series: resolvedSeries, + worklistID: this.props.worklistID, + existingData: serie => this.getExistingSeriesData(serie), + }); }; groupUnderPatient = (objArr) => { diff --git a/src/components/common/openSeriesHelper.js b/src/components/common/openSeriesHelper.js new file mode 100644 index 000000000..a69cec568 --- /dev/null +++ b/src/components/common/openSeriesHelper.js @@ -0,0 +1,96 @@ +import { + addToGrid, + getSingleSerie, + changeActivePort, + clearSelection, + jumpToAim, + alertViewPortFull, +} from '../annotationsList/action'; + +/** + * Finds a series by UID in the open series list. + * @returns {{ isOpen: boolean, index: number }} + */ +export const findInOpenSeries = (seriesUID, openSeries) => { + const index = openSeries.findIndex(s => s && s.seriesUID === seriesUID); + return { isOpen: index !== -1, index }; +}; + +/** + * Opens one or more DICOM series in the display view. + * + * Handles the dispatch+navigate sequence shared across all entry points: + * already-open detection, viewport capacity check, addToGrid/getSingleSerie + * dispatches, and navigation. Component-specific concerns — fetching series + * from the API, filtering by modality, managing modal state — remain in the + * calling component. + * + * @param {Object} options + * @param {Function} options.dispatch Redux dispatch + * @param {Function} options.navigate Navigate to /display; pass () => {} when already there + * @param {Array} options.openSeries Currently open series from Redux state + * @param {Array|Object} options.series Series object(s) to open; each may carry aimID/aimUID + * @param {string} [options.aimID] Aim ID — overrides per-series aimID/aimUID when provided + * @param {string} [options.worklistID] Passed through to addToGrid + * @param {Array|Function} [options.existingData] Cached image data for the study; pass a + * function (serie) => data for per-serie caching + * @param {Function} [options.onGridFull] Called with the pending series array when there + * is no room; defaults to alertViewPortFull + * @returns {Promise|undefined} + */ +export const openSeriesInDisplay = ({ + dispatch, + navigate, + openSeries, + series, + aimID = null, + worklistID = null, + existingData = null, + onGridFull = null, +}) => { + const maxPort = parseInt(sessionStorage.getItem('maxPort')); + const seriesArr = Array.isArray(series) ? series : [series]; + const toOpen = []; + + // Activate already-open series and, when an aim is provided, jump to it. + for (const serie of seriesArr) { + const { isOpen, index } = findInOpenSeries(serie.seriesUID, openSeries); + if (isOpen) { + dispatch(changeActivePort(index)); + const resolvedAimID = aimID || serie.aimID || serie.aimUID || null; + if (resolvedAimID) dispatch(jumpToAim(serie.seriesUID, resolvedAimID, index)); + } else { + toOpen.push(serie); + } + } + + // All series already open — just navigate. + if (toOpen.length === 0) { + dispatch(clearSelection()); + navigate(); + return Promise.resolve(); + } + + // Not enough free viewports. + if (toOpen.length + openSeries.length > maxPort) { + if (onGridFull) onGridFull(toOpen); + else dispatch(alertViewPortFull()); + return; + } + + const resolveExistingData = + typeof existingData === 'function' ? existingData : () => existingData; + + const promiseArr = toOpen.map(serie => { + const resolvedAimID = aimID || serie.aimID || serie.aimUID || null; + dispatch(addToGrid(serie, resolvedAimID, null, worklistID)); + return dispatch(getSingleSerie(serie, resolvedAimID, null, resolveExistingData(serie))); + }); + + return Promise.all(promiseArr) + .then(() => { + dispatch(clearSelection()); + navigate(); + }) + .catch(err => console.error(err)); +}; diff --git a/src/components/flexView/index.jsx b/src/components/flexView/index.jsx index b7a6ba756..62c682ffa 100644 --- a/src/components/flexView/index.jsx +++ b/src/components/flexView/index.jsx @@ -14,12 +14,11 @@ import SelectSerieModal from "../annotationsList/selectSerieModal"; import SeriesTable from "./SeriesTable"; import { isSupportedModality } from "../../Utils/aid.js"; import { - addToGrid, - getSingleSerie, clearSelection, setSeriesData, setLastLocation } from "../annotationsList/action"; +import { openSeriesInDisplay } from "../common/openSeriesHelper"; import "react-table-v6/react-table.css"; // import "../annotationSearch/annotationSearch.css"; // import "./flexView.css"; @@ -124,38 +123,20 @@ class FlexView extends React.Component { } catch (err) { console.log("Error => getting series of the study", err); } - if (this.props.openSeries.length === this.maxPort) { - this.setState({ showSeriesTable: true, series }); - return; - } - //get only unopen series - if (series.length > 0) series = this.excludeOpenSeries(series); - // filter series that have displayable modality series = series.filter(isSupportedModality); if (series.length === 0) { this.setState({ showWarning: true }); - } else { - //check if there is enough room - if (series.length + this.props.openSeries.length > this.maxPort) { - //if there is not bring the modal - this.setState({ showSeriesTable: true, series }); - // TODO show toast - } else { - //if there is enough room - //add serie to the grid - const promiseArr = []; - for (let i = 0; i < series.length; i++) { - this.props.dispatch(addToGrid(series[i])); - promiseArr.push(this.props.dispatch(getSingleSerie(series[i], null, null, existingData))); - } - //getsingleSerie - Promise.all(promiseArr) - .then(() => { - this.props.history.push("/display"); - }) - .catch((err) => console.error(err)); - } + return; } + + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => this.props.history.push("/display"), + openSeries: this.props.openSeries, + series, + existingData, + onGridFull: pending => this.setState({ showSeriesTable: true, series: pending }), + }); }; excludeOpenSeries = (allSeriesArr) => { diff --git a/src/components/searchView/Annotations.jsx b/src/components/searchView/Annotations.jsx index 602f07f85..cc1575e00 100644 --- a/src/components/searchView/Annotations.jsx +++ b/src/components/searchView/Annotations.jsx @@ -8,13 +8,11 @@ import { getAnnotations } from '../../services/annotationServices'; import { formatDate } from '../flexView/helperMethods'; import SelectSerieModal from '../annotationsList/selectSerieModal'; import { - getSingleSerie, clearSelection, selectAnnotation, changeActivePort, - addToGrid, - jumpToAim } from '../annotationsList/action'; +import { openSeriesInDisplay } from '../common/openSeriesHelper'; function Table({ columns, data }) { const { @@ -97,51 +95,16 @@ function Annotations(props) { }; const displayAnnotations = async (selected) => { - const { projectID, studyUID, seriesUID, aimID } = selected; + const { aimID } = selected; setSelected(selected); - const patientID = selected.subjectID; - const { openSeries } = props; - const maxPort = parseInt(sessionStorage.getItem('maxPort')); - // const serieObj = { projectID, patientID, studyUID, seriesUID, aimID }; - //check if there is enough space in the grid - let isGridFull = openSeries.length === maxPort; - //check if the serie is already open - if (checkIfSerieOpen(seriesUID).isOpen) { - const { index } = checkIfSerieOpen(seriesUID); - props.dispatch(changeActivePort(index)); - props.dispatch(jumpToAim(seriesUID, aimID, index)); - props.dispatch(clearSelection()); - props.history.push('/display'); - } else { - if (isGridFull) { - setShowSelectSerie(true); - } else { - props.dispatch(addToGrid(selected, aimID)); - props - .dispatch(getSingleSerie(selected, aimID)) - .then(() => { }) - .catch(err => console.error(err)); - //if grid is NOT full check if patient data exists - // -----> Delete after v1.0 <----- - // if (!props.patients[patientID]) { - // // props.dispatch(getWholeData(null, null, selected)); - // getWholeData(null, null, selected); - // } else { - // props.dispatch( - // updatePatient( - // 'annotation', - // true, - // patientID, - // studyUID, - // seriesUID, - // aimID - // ) - // ); - // } - props.dispatch(clearSelection()); - props.history.push('/display'); - } - } + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => props.history.push('/display'), + openSeries: props.openSeries, + series: selected, + aimID, + onGridFull: () => setShowSelectSerie(true), + }); }; const selectRow = (e, data) => { diff --git a/src/components/searchView/Report.jsx b/src/components/searchView/Report.jsx index 659cd1b28..d76ece8c0 100644 --- a/src/components/searchView/Report.jsx +++ b/src/components/searchView/Report.jsx @@ -13,10 +13,9 @@ import { changeActivePort, clearGrid, jumpToAim, - addToGrid, - getSingleSerie, updateImageId } from '../annotationsList/action'; +import { openSeriesInDisplay } from '../common/openSeriesHelper'; const maxPort = parseInt(sessionStorage.getItem('maxPort')); // let waterfallOptions = sessionStorage.getItem('waterfallOptions'); @@ -374,15 +373,16 @@ const Report = props => { const openAims = (seriesToOpen, projectID, patientID) => { try { setSelectedSeries(seriesToOpen); - const array = + const series = projectID && patientID ? seriesToOpen.map(el => ({ ...el, projectID, patientID })) : seriesToOpen; - for (let series of array) { - props.dispatch(addToGrid(series, series.aimID || series.aimUID)); - props.dispatch(getSingleSerie(series, series.aimID || series.aimUID)); - } - props.history.push('/display'); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => props.history.push('/display'), + openSeries: props.openSeries, + series, + }); } catch (err) { console.error(err); } diff --git a/src/components/searchView/Series.jsx b/src/components/searchView/Series.jsx index ba9045b7c..0ebf55ecc 100644 --- a/src/components/searchView/Series.jsx +++ b/src/components/searchView/Series.jsx @@ -9,16 +9,13 @@ import Annotations from "./Annotations"; import { getSeries } from "../../services/seriesServices"; import SelectSerieModal from "../annotationsList/selectSerieModal"; import { - alertViewPortFull, - getSingleSerie, - changeActivePort, selectSerie, clearSelection, - addToGrid, getWholeData, updatePatient, selectAnnotation, } from "../annotationsList/action"; +import { openSeriesInDisplay } from "../common/openSeriesHelper"; function Table({ columns, @@ -165,71 +162,21 @@ function Series(props) { const dispatchSerieDisplay = (selected) => { const { seriesData } = props; - const openSeries = Object.values(props.openSeries); const { patientID, studyUID, projectID } = selected; const dataExists = - seriesData[projectID] && - seriesData[projectID][patientID] && - seriesData[projectID][patientID][studyUID] && - seriesData[projectID][patientID][studyUID].list; - - const existingData = dataExists - ? seriesData[projectID][patientID][studyUID].list - : null; - - let isSerieOpen = false; - const maxPort = parseInt(sessionStorage.getItem("maxPort")); - - //check if there is enough space in the grid - let isGridFull = openSeries.length === maxPort; - //check if the serie is already open - - if (openSeries.length > 0) { - for (let i = 0; i < openSeries.length; i++) { - if (openSeries[i].seriesUID === selected.seriesUID) { - isSerieOpen = true; - props.dispatch(changeActivePort(i)); - break; - } - // } - } - } + seriesData[projectID] && + seriesData[projectID][patientID] && + seriesData[projectID][patientID][studyUID] && + seriesData[projectID][patientID][studyUID].list; - //serie is not already open; - if (!isSerieOpen) { - //if the grid is full show warning - if (isGridFull) { - setSerie(selected); - setShowSelectSerie(true); - // props.dispatch(alertViewPortFull()); - } else { - props.dispatch(addToGrid(selected)); - props - .dispatch(getSingleSerie(selected, null, null, existingData)) - .then(() => {}) - .catch((err) => console.error(err)); - //if grid is NOT full check if patient data exists - // -----> Delete after v1.0 <----- - // if (!props.patients[selected.patientID]) { - // props.dispatch(getWholeData(selected)); - // // getWholeData(selected); - // } else { - // props.dispatch( - // updatePatient( - // 'serie', - // true, - // patientID, - // studyUID, - // selected.seriesUID - // ) - // ); - // } - props.history.push("/display"); - } - } else { - props.history.push("/display"); - } - props.dispatch(clearSelection()); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => props.history.push("/display"), + openSeries: Object.values(props.openSeries), + series: selected, + existingData: dataExists ? seriesData[projectID][patientID][studyUID].list : null, + onGridFull: () => { setSerie(selected); setShowSelectSerie(true); }, + }); }; const handleCheckboxSelect = (row) => { diff --git a/src/components/searchView/Studies.jsx b/src/components/searchView/Studies.jsx index 648088f8c..cfcc74e05 100644 --- a/src/components/searchView/Studies.jsx +++ b/src/components/searchView/Studies.jsx @@ -11,13 +11,11 @@ import { formatDate } from "../flexView/helperMethods"; import { getSeries } from "../../services/seriesServices"; import { clearCarets, isSupportedModality } from "../../Utils/aid.js"; import { - getSingleSerie, selectStudy, clearSelection, startLoading, loadCompleted, annotationsLoadingError, - addToGrid, getWholeData, alertViewPortFull, updatePatient, @@ -25,6 +23,7 @@ import { selectAnnotation, setSeriesData, } from "../annotationsList/action"; +import { openSeriesInDisplay } from "../common/openSeriesHelper"; function Table({ columns, @@ -216,61 +215,21 @@ function Studies(props) { }; const displaySeries = async (selected) => { - const maxPort = parseInt(sessionStorage.getItem("maxPort")); - const { patientID, studyUID } = selected; let seriesArr = await getSeriesData(selected); - const list = seriesArr.length > 0 ? seriesArr : null; - //check if the patient is there (create a patient exist flag) - // const patientExists = props.patients[patientID]; - //if there is patient iterate over the series object of the study (form an array of series) - // if (patientExists) { - // seriesArr = Object.values( - // props.patients[patientID].studies[studyUID].series - // ); - // //if there is not a patient get series data of the study and (form an array of series) - // } else { - - // } - // filter the nondisplayable modalities seriesArr = seriesArr.filter(isSupportedModality); - //get extraction of the series (extract unopen series) - // if (seriesArr.length > 0) seriesArr = excludeOpenSeries(seriesArr); - //check if there is enough room - if (seriesArr.length + props.openSeries.length > maxPort) { - //if there is not bring the modal - // await setState({ - // isSerieSelectionOpen: true, - // selectedStudy: [seriesArr], - // studyName: selected.studyDescription - // }); - setIsSerieSelectionOpen(true); - setSelectedStudy([seriesArr]); - setStudyName(selected.studyDescription); - } else { - //if there is enough room - //add serie to the grid - const promiseArr = []; - for (let serie of seriesArr) { - props.dispatch(addToGrid(serie)); - promiseArr.push(props.dispatch(getSingleSerie(serie, null, null, list))); - } - //getsingleSerie - Promise.all(promiseArr) - .then(() => {}) - .catch((err) => console.error(err)); - //if patient doesnot exist get patient - // -----> Delete after v1.0 <----- - // if (!patientExists) { - // // props.dispatch(getWholeData(null, selected)); - // getWholeData(null, selected); - // } else { - // //check if study exist - // props.dispatch(updatePatient('study', true, patientID, studyUID)); - // } - props.history.push("/display"); - } - props.dispatch(clearSelection()); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => props.history.push("/display"), + openSeries: props.openSeries, + series: seriesArr, + existingData: seriesArr.length > 0 ? seriesArr : null, + onGridFull: pending => { + setIsSerieSelectionOpen(true); + setSelectedStudy([pending]); + setStudyName(selected.studyDescription); + }, + }); }; const columns = React.useMemo( diff --git a/src/components/sideBar/sideBarWorklist.jsx b/src/components/sideBar/sideBarWorklist.jsx index a8f23fc08..6bfcc586a 100644 --- a/src/components/sideBar/sideBarWorklist.jsx +++ b/src/components/sideBar/sideBarWorklist.jsx @@ -24,7 +24,8 @@ import { getSeries } from "../../services/seriesServices"; // Component imports import DeleteAlert from "../management/common/alertDeletionModal"; import SelectSeriesModal from "../annotationsList/selectSerieModal"; -import { addToGrid, getSingleSerie, alertViewPortFull, clearSelection, changeActivePort, selectPatient, setSeriesData, clearGrid } from "../annotationsList/action"; +import { alertViewPortFull, clearSelection, changeActivePort, selectPatient, setSeriesData, clearGrid } from "../annotationsList/action"; +import { openSeriesInDisplay } from "../common/openSeriesHelper"; import { isSupportedModality, filterProjects, pseudo, generalizeDate } from "../../Utils/aid.js"; // CSS import import "./style.css"; @@ -189,49 +190,21 @@ let seriesCallSent; } const viewSelection = async (seriesArr) => { - const { seriesData } = props; + if (!seriesArr.length) return; const maxPort = parseInt(sessionStorage.getItem("maxPort")); - const notOpenSeries = []; - // const selectedSeries = Object.values(seriesObj); - const selectedSeries = seriesArr; - if (selectedSeries.length > 0) { - //check if enough room to display selection - for (let serie of selectedSeries) { - if (!checkIfSerieOpen(serie.seriesUID).isOpen) { - notOpenSeries.push(serie); - } - } - //if all ports are full - if ( - notOpenSeries.length > 0 && - props.openSeries.length === maxPort - ) { - props.dispatch(alertViewPortFull()); - } else { - //if all series already open update active port - if (notOpenSeries.length === 0) { - let index = checkIfSerieOpen(selectedSeries[0].seriesUID).index; - props.dispatch(changeActivePort(index)); - props.history.push("/display"); - props.dispatch(clearSelection()); - } else { - if (selectedSeries.length + props.openSeries.length > maxPort) { - // alert user about the num of open series a the moment and told only maxPort is allowed - const openPorts = props.openSeries.length; - setError(`Already ${openPorts} viewers open. You can open ${maxPort} at a time`); - } else { - //else get data for each serie for display - selectedSeries.forEach((serie) => { - const list = getExistingSeriesData(serie); - props.dispatch(addToGrid(serie, null, null, props.match.params.wid)); - props.dispatch(getSingleSerie(serie, null, null, list)); - }); - props.history.push("/display"); - props.dispatch(clearSelection()); - } - } - } - } + + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => props.history.push("/display"), + openSeries: props.openSeries, + series: seriesArr, + worklistID: props.match.params.wid, + existingData: serie => getExistingSeriesData(serie), + onGridFull: pending => { + const openPorts = props.openSeries.length; + setError(`Already ${openPorts} viewers open. You can open ${maxPort} at a time`); + }, + }); }; const handleOpenClick = async (study) => { From 9ebe3143ac4b2882638a329c822e9f39e96ed5e1 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Sat, 18 Apr 2026 14:27:12 -0700 Subject: [PATCH 04/34] Implement mammogram viewing --- .../annotationSearch/AnnotationTable.jsx | 39 +++++++++--- src/components/annotationsList/action.js | 17 ++++++ src/components/annotationsList/reducer.js | 17 ++++++ src/components/annotationsList/types.js | 3 + src/components/common/openSeriesHelper.js | 10 ++++ src/components/display/displayView.jsx | 59 ++++++++++++++++++- src/components/flexView/index.jsx | 18 +++++- src/components/searchView/Studies.jsx | 16 ++++- 8 files changed, 165 insertions(+), 14 deletions(-) diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index ee3a7d9c1..cbd8be283 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -18,9 +18,10 @@ import { annotationsLoadingError, updateSearchTableIndex, setSeriesData, - storeAimSelection + storeAimSelection, + setMammogramSeries, } from "../annotationsList/action"; -import { openSeriesInDisplay } from "../common/openSeriesHelper"; +import { openSeriesInDisplay, isMammogramStudy } from "../common/openSeriesHelper"; import { formatDate } from "../flexView/helperMethods"; import { getSeries, getSignificantSeries } from "../../services/seriesServices"; import SelectSerieModal from "../annotationsList/selectSerieModal"; @@ -532,11 +533,33 @@ function AnnotationTable(props) { const displaySeries = async (selected) => { const { subjectID: patientID, studyUID, aimID, projectID, template } = selected; let isTeachingFile = teachingFileTempCode === template; - let seriesArr = []; + let seriesArr = []; let existingData = getExistingData(selected); - + try { - if (isTeachingFile) { + // Mammogram studies bypass significant-series and teaching-file logic entirely. + // Always fetch all series and load the first MAMMO_PAGE_SIZE into viewports. + if (selected.modality === 'MG' || selected.examType === 'MG') { + seriesArr = await getSeriesData(selected); + const filtered = Array.isArray(seriesArr) ? seriesArr.filter(isSupportedModality) : []; + if (isMammogramStudy(filtered)) { + props.dispatch(setMammogramSeries(filtered, studyUID)); + const firstPage = filtered.slice(0, maxPort); + setSelected(firstPage); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: props.switchToDisplay, + openSeries: props.openSeries, + series: firstPage, + aimID, + existingData: filtered, + onGridFull: () => setShowSelectSeriesModal(true), + }); + return; + } + // Fell through: modality field was MG but series say otherwise — use fetched data as-is + seriesArr = filtered; + } else if (isTeachingFile) { seriesArr = await getSignificantSeriesData(selected); if (seriesArr.length > 0){ seriesArr = seriesArr.map( el => ({...el, patientID, studyUID, projectID, template }));} @@ -544,15 +567,13 @@ function AnnotationTable(props) { seriesArr = existingData; } else if (existingData && existingData.length > maxPort) { seriesArr = existingData.slice(0,maxPort); - // setSelected(seriesArr); - // setShowSelectSeriesModal(true); } else { seriesArr = await getSeriesData(selected, true); seriesArr = seriesArr.slice(0,maxPort); } - } else + } else { seriesArr = await getSeriesData(selected); - + } } catch (err) { setShowSpinner(false); } diff --git a/src/components/annotationsList/action.js b/src/components/annotationsList/action.js index df042e6da..3bbf9909e 100644 --- a/src/components/annotationsList/action.js +++ b/src/components/annotationsList/action.js @@ -56,6 +56,9 @@ import { TOGGLE_ALL_CALCULATIONS, SET_LAST_LOCATION, SHOW_PHI, + SET_MAMMOGRAM_SERIES, + SET_MAMMOGRAM_PAGE, + CLEAR_MAMMOGRAM_SERIES, colors, commonLabels, } from "./types"; @@ -1202,6 +1205,20 @@ export const aimDelete = (aimRefs) => { return { type: AIM_DELETE, payload: aimRefs }; }; +export const setMammogramSeries = (allSeries, studyUID) => ({ + type: SET_MAMMOGRAM_SERIES, + payload: { allSeries, studyUID }, +}); + +export const setMammogramPage = (pageIndex) => ({ + type: SET_MAMMOGRAM_PAGE, + payload: pageIndex, +}); + +export const clearMammogramSeries = () => ({ + type: CLEAR_MAMMOGRAM_SERIES, +}); + export const otherAimsUpdated = (seriesList, aimRefs) => { return { type: AIM_SAVE, payload: { seriesList, aimRefs } }; diff --git a/src/components/annotationsList/reducer.js b/src/components/annotationsList/reducer.js index 4fcf7d895..9c6e4dbbf 100644 --- a/src/components/annotationsList/reducer.js +++ b/src/components/annotationsList/reducer.js @@ -57,6 +57,9 @@ import { TOGGLE_ALL_CALCULATIONS, SET_LAST_LOCATION, SHOW_PHI, + SET_MAMMOGRAM_SERIES, + SET_MAMMOGRAM_PAGE, + CLEAR_MAMMOGRAM_SERIES, colors, commonLabels, } from "./types"; @@ -112,6 +115,9 @@ const initialState = { showAnnotations: mode === 'teaching' ? false : true, lastLocation: '', showingPHI: isPHIVisible || false, + mammogramSeries: [], + mammogramStudyUID: null, + mammogramPageIndex: 0, }; @@ -1031,6 +1037,17 @@ const asyncReducer = (state = initialState, action) => { } return { ...state, openSeriesAddition: newOpenSeriesAddition, otherSeriesAimsList: deepOther }; } + case SET_MAMMOGRAM_SERIES: + return { + ...state, + mammogramSeries: action.payload.allSeries, + mammogramStudyUID: action.payload.studyUID, + mammogramPageIndex: 0, + }; + case SET_MAMMOGRAM_PAGE: + return { ...state, mammogramPageIndex: action.payload }; + case CLEAR_MAMMOGRAM_SERIES: + return { ...state, mammogramSeries: [], mammogramStudyUID: null, mammogramPageIndex: 0 }; default: return state; } diff --git a/src/components/annotationsList/types.js b/src/components/annotationsList/types.js index 3517cdbea..1a5a08b75 100644 --- a/src/components/annotationsList/types.js +++ b/src/components/annotationsList/types.js @@ -69,6 +69,9 @@ export const STORE_AIM_SELECTION_ALL = 'epadjs/annotationList/STORE_AIM_SELECTIO export const TOGGLE_ALL_CALCULATIONS = 'epadjs/annotationList/TOGGLE_ALL_CALCULATIONS'; export const SET_LAST_LOCATION = 'epadjs/annotationList/SET_LAST_LOCATION'; export const SHOW_PHI = 'epadjs/annotationList/SHOW_PHI'; +export const SET_MAMMOGRAM_SERIES = 'epadjs/annotationList/SET_MAMMOGRAM_SERIES'; +export const SET_MAMMOGRAM_PAGE = 'epadjs/annotationList/SET_MAMMOGRAM_PAGE'; +export const CLEAR_MAMMOGRAM_SERIES = 'epadjs/annotationList/CLEAR_MAMMOGRAM_SERIES'; export const commonLabels = { button: { background: "#6c757d", color: "#ECECEC", border: "#acacac solid 1px" }, diff --git a/src/components/common/openSeriesHelper.js b/src/components/common/openSeriesHelper.js index a69cec568..bf28983d9 100644 --- a/src/components/common/openSeriesHelper.js +++ b/src/components/common/openSeriesHelper.js @@ -7,6 +7,16 @@ import { alertViewPortFull, } from '../annotationsList/action'; +/** + * Returns true when the app is in lite mode AND every series in the filtered + * array is a mammogram (MG). Pass only the isSupportedModality-filtered list. + */ +export const isMammogramStudy = (seriesArray) => { + if (sessionStorage.getItem('mode') !== 'lite') return false; + if (!Array.isArray(seriesArray) || seriesArray.length === 0) return false; + return seriesArray.every(s => s.examType === 'MG'); +}; + /** * Finds a series by UID in the open series list. * @returns {{ isOpen: boolean, index: number }} diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index 4ab4db97b..3c2f00437 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -28,19 +28,21 @@ import { aimDelete, changeActivePort, clearAimId, + clearGrid, clearMultiFrameAimJumpFlags, clearSelection, closeSerie, getSingleSerie, jumpToAim, setSegLabelMapIndex, - setSeriesData + setSeriesData, + setMammogramPage, // fillSeriesDescfullData - , updateGridWithMultiFrameInfo, updateImageId, updateSubpath } from "../annotationsList/action"; +import { openSeriesInDisplay } from "../common/openSeriesHelper"; import { arrow } from "./Arrow"; import { bidirectional } from "./Bidirectional"; import { circle } from "./Circle"; @@ -162,6 +164,8 @@ const mapStateToProps = (state) => { lastLocation: state.annotationsListReducer.lastLocation, projectMap: state.annotationsListReducer.projectMap, showingPHI: state.annotationsListReducer.showingPHI, + mammogramSeries: state.annotationsListReducer.mammogramSeries, + mammogramPageIndex: state.annotationsListReducer.mammogramPageIndex, }; }; @@ -2965,6 +2969,47 @@ class DisplayView extends Component { } }; + // --- Mammogram pagination --- + + /** True when the currently open series are all mammograms and there is stored page data. */ + isMammogramOpen = () => { + const { series, mammogramSeries } = this.props; + return ( + mammogramSeries && + mammogramSeries.length > 0 && + series && + series.some(s => s && s.examType === 'MG') + ); + }; + + /** True when there is at least one more page of mammogram series to load. */ + hasNextMammoPage = () => { + const { mammogramSeries, mammogramPageIndex } = this.props; + return (mammogramPageIndex + 1) * parseInt(maxPort) < mammogramSeries.length; + }; + + /** Clears the grid and loads the next group of MAMMO_PAGE_SIZE series. */ + handleMammoNext = () => { + const { mammogramSeries, mammogramPageIndex } = this.props; + const nextPage = mammogramPageIndex + 1; + const pageSize = parseInt(maxPort); + const start = nextPage * pageSize; + const nextSeries = mammogramSeries.slice(start, start + pageSize); + if (!nextSeries.length) return; + + this.props.dispatch(clearGrid()); + this.props.dispatch(setMammogramPage(nextPage)); + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => {}, // already in display view + openSeries: [], // grid was just cleared + series: nextSeries, + existingData: mammogramSeries, + }); + }; + + // --- End mammogram pagination --- + openNextWLStudy = async (worklistID, studyUID) => { try { const sortedData = JSON.parse(sessionStorage.getItem("sortedListMap")) || {}; @@ -3045,6 +3090,16 @@ class DisplayView extends Component { onOpenSeries={this.props.openSeries} openNextWLStudy={this.openNextWLStudy} /> + {this.isMammogramOpen() && ( + + )} {this.state.isLoading && (
this.props.history.push("/display"), + openSeries: this.props.openSeries, + series: series.slice(0, parseInt(maxPort)), + existingData: series, + }); + return; + } + openSeriesInDisplay({ dispatch: this.props.dispatch, navigate: () => this.props.history.push("/display"), diff --git a/src/components/searchView/Studies.jsx b/src/components/searchView/Studies.jsx index cfcc74e05..c6749b431 100644 --- a/src/components/searchView/Studies.jsx +++ b/src/components/searchView/Studies.jsx @@ -22,8 +22,9 @@ import { selectSerie, selectAnnotation, setSeriesData, + setMammogramSeries, } from "../annotationsList/action"; -import { openSeriesInDisplay } from "../common/openSeriesHelper"; +import { openSeriesInDisplay, isMammogramStudy } from "../common/openSeriesHelper"; function Table({ columns, @@ -218,6 +219,19 @@ function Studies(props) { let seriesArr = await getSeriesData(selected); seriesArr = seriesArr.filter(isSupportedModality); + // Mammogram studies load only the first page; remaining series paginate via NEXT. + if (isMammogramStudy(seriesArr)) { + props.dispatch(setMammogramSeries(seriesArr, selected.studyUID)); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => props.history.push("/display"), + openSeries: props.openSeries, + series: seriesArr.slice(0, parseInt(sessionStorage.getItem('maxPort'))), + existingData: seriesArr, + }); + return; + } + openSeriesInDisplay({ dispatch: props.dispatch, navigate: () => props.history.push("/display"), From a279c559e6488beee86f1cd649ee64b2996a7b0b Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Sat, 18 Apr 2026 15:02:31 -0700 Subject: [PATCH 05/34] Add Mammogramm specific tools --- src/components/ToolMenu/ToolMenu.css | 41 ++++++++++++++++++++++ src/components/display/displayView.jsx | 47 +++++++++++++++++++++----- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/src/components/ToolMenu/ToolMenu.css b/src/components/ToolMenu/ToolMenu.css index 25ff075c1..4494a856b 100644 --- a/src/components/ToolMenu/ToolMenu.css +++ b/src/components/ToolMenu/ToolMenu.css @@ -105,4 +105,45 @@ .input-range__track { height: 0.5rem; +} + +/* Mammogram toolbar group — sits at the trailing end of the toolbar */ +.mammo-toolbar-group { + display: inline-flex; + align-items: center; + background-color: #2a4a6b; + border-radius: 4px; + padding: 2px 6px; + margin-left: 10px; + border-left: 2px solid #4a7ab5; +} + +.mammo-toolbar-btn { + cursor: pointer; + display: inline-block; + min-width: 40px; + outline: none; + text-align: center; + margin-left: 4px; + color: white; + font-size: 11px; + background: none; + border: none; + padding: 2px 4px; + transition: all 0.2s ease-in-out; +} + +.mammo-toolbar-btn:first-child { + margin-left: 0; +} + +.mammo-toolbar-btn:hover:not(:disabled) { + color: #aadcff; + transform: scale(1.1); +} + +.mammo-toolbar-btn:disabled, +.mammo-toolbar-btn--disabled { + opacity: 0.4; + cursor: not-allowed; } \ No newline at end of file diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index 3c2f00437..2c4181cf4 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -3008,6 +3008,18 @@ class DisplayView extends Component { }); }; + /** + * Expands the active viewport (single-viewport view). + * When Feature 3 (dual-viewport dot selection) is implemented, this will + * also handle the two-selected-viewports case. + */ + handleMammoExpand = () => { + const { activePort } = this.props; + // Feature 3: check for selected viewports via selection dot (TBD). + // For now, maximise the active viewport by updating the port layout. + this.props.dispatch({ type: 'EXPAND_VIEWPORT', payload: activePort }); + }; + // --- End mammogram pagination --- openNextWLStudy = async (worklistID, studyUID) => { @@ -3091,14 +3103,33 @@ class DisplayView extends Component { openNextWLStudy={this.openNextWLStudy} /> {this.isMammogramOpen() && ( - +
+ + + +
)} {this.state.isLoading && (
From 66c6a0c7c7099c402bb80b60400d1157617baed3 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Sat, 18 Apr 2026 20:50:08 -0700 Subject: [PATCH 06/34] Implement expand functionality --- src/components/display/displayView.jsx | 100 ++++++++++++++++++++++--- src/components/display/viewport.css | 19 +++++ 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index 2c4181cf4..ed96435f7 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -6,7 +6,7 @@ import * as cornerstoneWADOImageLoader from "cornerstone-wado-image-loader"; import * as dcmjs from "dcmjs"; import _ from "lodash"; import CornerstoneViewport from "react-cornerstone-viewport"; -import { FaExpandArrowsAlt, FaPen, FaTag, FaTimes } from "react-icons/fa"; +import { FaExpandArrowsAlt, FaPen, FaTag, FaTimes, FaRegSquare, FaCheckSquare } from "react-icons/fa"; import { connect } from "react-redux"; import { Redirect } from "react-router"; import { withRouter } from "react-router-dom"; @@ -208,6 +208,8 @@ class DisplayView extends Component { dataIndexMap: {}, aimEdited: false, isVisible: true, + selectedPorts: new Set(), + mammoExpanded: false, }; } @@ -341,6 +343,12 @@ class DisplayView extends Component { return; } + // Clear mammogram dot selections when a new study replaces the current one. + if (prevProps.mammogramSeries !== this.props.mammogramSeries && + (this.state.selectedPorts.size > 0 || this.state.mammoExpanded)) { + this.clearMammoSelection(); + } + const { projectID, studyUID } = series[activePort]; let { seriesUID } = series[activePort]; if (cornerstone.getEnabledElements()[activePort]) { @@ -2997,6 +3005,7 @@ class DisplayView extends Component { const nextSeries = mammogramSeries.slice(start, start + pageSize); if (!nextSeries.length) return; + this.clearMammoSelection(); this.props.dispatch(clearGrid()); this.props.dispatch(setMammogramPage(nextPage)); openSeriesInDisplay({ @@ -3008,16 +3017,79 @@ class DisplayView extends Component { }); }; + /** Toggle selection of a viewport dot. Max 2 at a time. */ + handleMammoDotClick = (index) => { + const { selectedPorts } = this.state; + const next = new Set(selectedPorts); + if (next.has(index)) { + next.delete(index); + } else { + if (next.size >= 2) { + toast.warn("Only two viewports can be selected at a time.", { + position: "top-right", + autoClose: 3000, + }); + return; + } + next.add(index); + } + this.setState({ selectedPorts: next }); + }; + /** - * Expands the active viewport (single-viewport view). - * When Feature 3 (dual-viewport dot selection) is implemented, this will - * also handle the two-selected-viewports case. + * EXPAND: if two viewports are selected, show them side by side. + * Otherwise fall back to the standard single-viewport hideShow. + * RESTORE: return to the standard grid layout. */ handleMammoExpand = () => { const { activePort } = this.props; - // Feature 3: check for selected viewports via selection dot (TBD). - // For now, maximise the active viewport by updating the port layout. - this.props.dispatch({ type: 'EXPAND_VIEWPORT', payload: activePort }); + const { selectedPorts, mammoExpanded, containerHeight } = this.state; + + if (mammoExpanded) { + this.restoreMammoExpand(); + return; + } + + if (selectedPorts.size === 2) { + const selectedArr = Array.from(selectedPorts); + const elements = document.getElementsByClassName("viewportContainer"); + for (let i = 0; i < elements.length; i++) { + elements[i].style.display = selectedArr.includes(i) ? "inline-block" : "none"; + } + this.setState( + { mammoExpanded: true, width: "50%", height: containerHeight }, + () => window.dispatchEvent(new CustomEvent("resize", { detail: { isMaximize: true } })) + ); + } else { + this.hideShow(activePort); + } + }; + + /** Restore all viewports to the standard grid layout and clear dot selections. */ + restoreMammoExpand = () => { + const elements = document.getElementsByClassName("viewportContainer"); + for (let i = 0; i < elements.length; i++) { + elements[i].style.display = "inline-block"; + } + this.setState( + { mammoExpanded: false, selectedPorts: new Set() }, + () => { + this.getViewports(); + window.dispatchEvent(new CustomEvent("resize", { detail: { isMaximize: false } })); + } + ); + }; + + /** Clear mammogram selection state (dots + expand). Called on NEXT and new study. */ + clearMammoSelection = () => { + if (this.state.mammoExpanded) { + // Restore hidden viewports before clearing state + const elements = document.getElementsByClassName("viewportContainer"); + for (let i = 0; i < elements.length; i++) { + elements[i].style.display = "inline-block"; + } + } + this.setState({ selectedPorts: new Set(), mammoExpanded: false }); }; // --- End mammogram pagination --- @@ -3116,10 +3188,10 @@ class DisplayView extends Component {
+ {this.isMammogramOpen() && ( + { e.stopPropagation(); this.handleMammoDotClick(i); }} + title={this.state.selectedPorts.has(i) ? "Deselect viewport" : "Select viewport for expand"} + > + {this.state.selectedPorts.has(i) ? : } + + )} Date: Sun, 19 Apr 2026 21:12:42 -0700 Subject: [PATCH 07/34] Add stamp --- src/components/common/openSeriesHelper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/common/openSeriesHelper.js b/src/components/common/openSeriesHelper.js index bf28983d9..1bd238f32 100644 --- a/src/components/common/openSeriesHelper.js +++ b/src/components/common/openSeriesHelper.js @@ -12,6 +12,7 @@ import { * array is a mammogram (MG). Pass only the isSupportedModality-filtered list. */ export const isMammogramStudy = (seriesArray) => { + console.log(" ------ Checking mammogram ------"); if (sessionStorage.getItem('mode') !== 'lite') return false; if (!Array.isArray(seriesArray) || seriesArray.length === 0) return false; return seriesArray.every(s => s.examType === 'MG'); From dc073e0986b101f0401495b7b106bd0a698be4db Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Sun, 19 Apr 2026 21:36:03 -0700 Subject: [PATCH 08/34] Add consoles --- src/components/common/openSeriesHelper.js | 4 +++- src/components/display/displayView.jsx | 10 ++++------ src/components/searchView/Studies.jsx | 8 +++++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/components/common/openSeriesHelper.js b/src/components/common/openSeriesHelper.js index 1bd238f32..cb995d7d5 100644 --- a/src/components/common/openSeriesHelper.js +++ b/src/components/common/openSeriesHelper.js @@ -15,7 +15,9 @@ export const isMammogramStudy = (seriesArray) => { console.log(" ------ Checking mammogram ------"); if (sessionStorage.getItem('mode') !== 'lite') return false; if (!Array.isArray(seriesArray) || seriesArray.length === 0) return false; - return seriesArray.every(s => s.examType === 'MG'); + const mGVerified = seriesArray.every(s => s.examType === 'MG'); + console.log(mGVerified) + return mGVerified; }; /** diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index ed96435f7..df4ae3d84 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -2982,12 +2982,10 @@ class DisplayView extends Component { /** True when the currently open series are all mammograms and there is stored page data. */ isMammogramOpen = () => { const { series, mammogramSeries } = this.props; - return ( - mammogramSeries && - mammogramSeries.length > 0 && - series && - series.some(s => s && s.examType === 'MG') - ); + const hasMammoSeries = !!(mammogramSeries && mammogramSeries.length > 0); + const hasOpenMG = !!(series && series.some(s => s && s.examType === 'MG')); + console.log('[MG Debug] isMammogramOpen — mammogramSeries.length:', mammogramSeries?.length, '| hasOpenMG:', hasOpenMG, '| result:', hasMammoSeries && hasOpenMG); + return hasMammoSeries && hasOpenMG; }; /** True when there is at least one more page of mammogram series to load. */ diff --git a/src/components/searchView/Studies.jsx b/src/components/searchView/Studies.jsx index c6749b431..d774ea0b1 100644 --- a/src/components/searchView/Studies.jsx +++ b/src/components/searchView/Studies.jsx @@ -217,10 +217,16 @@ function Studies(props) { const displaySeries = async (selected) => { let seriesArr = await getSeriesData(selected); + console.log('[MG Debug] raw seriesArr before filter:', seriesArr?.map(s => ({ uid: s.seriesUID, examType: s.examType }))); seriesArr = seriesArr.filter(isSupportedModality); + console.log('[MG Debug] after isSupportedModality filter:', seriesArr?.map(s => ({ uid: s.seriesUID, examType: s.examType }))); + console.log('[MG Debug] mode in sessionStorage:', sessionStorage.getItem('mode')); + console.log('[MG Debug] maxPort in sessionStorage:', sessionStorage.getItem('maxPort')); // Mammogram studies load only the first page; remaining series paginate via NEXT. - if (isMammogramStudy(seriesArr)) { + const isMammo = isMammogramStudy(seriesArr); + console.log('[MG Debug] isMammogramStudy result:', isMammo); + if (isMammo) { props.dispatch(setMammogramSeries(seriesArr, selected.studyUID)); openSeriesInDisplay({ dispatch: props.dispatch, From 6c12151a9ec57e902e8c6b6833d75bec00bb80a4 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Sun, 19 Apr 2026 22:37:31 -0700 Subject: [PATCH 09/34] Fix case sensitivity at modality check --- .../annotationSearch/AnnotationTable.jsx | 3 ++- src/components/common/openSeriesHelper.js | 4 ++-- src/components/display/displayView.jsx | 2 +- src/components/searchView/Studies.jsx | 8 +------- src/components/sideBar/sideBarWorklist.jsx | 18 ++++++++++++++++-- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index cbd8be283..cda5b3e41 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -532,6 +532,7 @@ function AnnotationTable(props) { // CHECK const displaySeries = async (selected) => { const { subjectID: patientID, studyUID, aimID, projectID, template } = selected; + console.log(selected); let isTeachingFile = teachingFileTempCode === template; let seriesArr = []; let existingData = getExistingData(selected); @@ -539,7 +540,7 @@ function AnnotationTable(props) { try { // Mammogram studies bypass significant-series and teaching-file logic entirely. // Always fetch all series and load the first MAMMO_PAGE_SIZE into viewports. - if (selected.modality === 'MG' || selected.examType === 'MG') { + if (selected.modality?.toUpperCase() === 'MG' || selected.examType?.toUpperCase() === 'MG') { seriesArr = await getSeriesData(selected); const filtered = Array.isArray(seriesArr) ? seriesArr.filter(isSupportedModality) : []; if (isMammogramStudy(filtered)) { diff --git a/src/components/common/openSeriesHelper.js b/src/components/common/openSeriesHelper.js index cb995d7d5..86f9d1d3c 100644 --- a/src/components/common/openSeriesHelper.js +++ b/src/components/common/openSeriesHelper.js @@ -15,8 +15,8 @@ export const isMammogramStudy = (seriesArray) => { console.log(" ------ Checking mammogram ------"); if (sessionStorage.getItem('mode') !== 'lite') return false; if (!Array.isArray(seriesArray) || seriesArray.length === 0) return false; - const mGVerified = seriesArray.every(s => s.examType === 'MG'); - console.log(mGVerified) + const mGVerified = seriesArray.every(s => s.examType?.toUpperCase() === 'MG'); + console.log(' ---> mGVerified', mGVerified) return mGVerified; }; diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index df4ae3d84..f0067862d 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -2983,7 +2983,7 @@ class DisplayView extends Component { isMammogramOpen = () => { const { series, mammogramSeries } = this.props; const hasMammoSeries = !!(mammogramSeries && mammogramSeries.length > 0); - const hasOpenMG = !!(series && series.some(s => s && s.examType === 'MG')); + const hasOpenMG = !!(series && series.some(s => s && s.examType?.toUpperCase() === 'MG')); console.log('[MG Debug] isMammogramOpen — mammogramSeries.length:', mammogramSeries?.length, '| hasOpenMG:', hasOpenMG, '| result:', hasMammoSeries && hasOpenMG); return hasMammoSeries && hasOpenMG; }; diff --git a/src/components/searchView/Studies.jsx b/src/components/searchView/Studies.jsx index d774ea0b1..c6749b431 100644 --- a/src/components/searchView/Studies.jsx +++ b/src/components/searchView/Studies.jsx @@ -217,16 +217,10 @@ function Studies(props) { const displaySeries = async (selected) => { let seriesArr = await getSeriesData(selected); - console.log('[MG Debug] raw seriesArr before filter:', seriesArr?.map(s => ({ uid: s.seriesUID, examType: s.examType }))); seriesArr = seriesArr.filter(isSupportedModality); - console.log('[MG Debug] after isSupportedModality filter:', seriesArr?.map(s => ({ uid: s.seriesUID, examType: s.examType }))); - console.log('[MG Debug] mode in sessionStorage:', sessionStorage.getItem('mode')); - console.log('[MG Debug] maxPort in sessionStorage:', sessionStorage.getItem('maxPort')); // Mammogram studies load only the first page; remaining series paginate via NEXT. - const isMammo = isMammogramStudy(seriesArr); - console.log('[MG Debug] isMammogramStudy result:', isMammo); - if (isMammo) { + if (isMammogramStudy(seriesArr)) { props.dispatch(setMammogramSeries(seriesArr, selected.studyUID)); openSeriesInDisplay({ dispatch: props.dispatch, diff --git a/src/components/sideBar/sideBarWorklist.jsx b/src/components/sideBar/sideBarWorklist.jsx index 6bfcc586a..54509eee9 100644 --- a/src/components/sideBar/sideBarWorklist.jsx +++ b/src/components/sideBar/sideBarWorklist.jsx @@ -24,8 +24,8 @@ import { getSeries } from "../../services/seriesServices"; // Component imports import DeleteAlert from "../management/common/alertDeletionModal"; import SelectSeriesModal from "../annotationsList/selectSerieModal"; -import { alertViewPortFull, clearSelection, changeActivePort, selectPatient, setSeriesData, clearGrid } from "../annotationsList/action"; -import { openSeriesInDisplay } from "../common/openSeriesHelper"; +import { alertViewPortFull, clearSelection, changeActivePort, selectPatient, setSeriesData, clearGrid, setMammogramSeries } from "../annotationsList/action"; +import { openSeriesInDisplay, isMammogramStudy } from "../common/openSeriesHelper"; import { isSupportedModality, filterProjects, pseudo, generalizeDate } from "../../Utils/aid.js"; // CSS import import "./style.css"; @@ -230,6 +230,20 @@ let seriesCallSent; series = series.filter(isSupportedModality); const maxPort = parseInt(sessionStorage.getItem("maxPort")); const { openSeries } = props; + + if (isMammogramStudy(series)) { + props.dispatch(setMammogramSeries(series, studyUID)); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: () => props.history.push("/display"), + openSeries: props.openSeries, + series: series.slice(0, maxPort), + worklistID: props.match.params.wid, + existingData: series, + }); + return; + } + const alreadyOpenViews = isTeaching ? 0 : openSeries.length; if (alreadyOpenViews + series.length <= maxPort) { setSeries(series); From 0e20ea20493632dcd4416a30deb3c606d9cefc5e Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 20 Apr 2026 11:46:40 -0700 Subject: [PATCH 10/34] Improve mode and mammogram checks --- .../annotationSearch/AnnotationTable.jsx | 44 ++++++++----------- src/components/common/openSeriesHelper.js | 2 +- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index cda5b3e41..84c227b70 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -538,29 +538,25 @@ function AnnotationTable(props) { let existingData = getExistingData(selected); try { - // Mammogram studies bypass significant-series and teaching-file logic entirely. - // Always fetch all series and load the first MAMMO_PAGE_SIZE into viewports. - if (selected.modality?.toUpperCase() === 'MG' || selected.examType?.toUpperCase() === 'MG') { - seriesArr = await getSeriesData(selected); - const filtered = Array.isArray(seriesArr) ? seriesArr.filter(isSupportedModality) : []; - if (isMammogramStudy(filtered)) { - props.dispatch(setMammogramSeries(filtered, studyUID)); - const firstPage = filtered.slice(0, maxPort); - setSelected(firstPage); - openSeriesInDisplay({ - dispatch: props.dispatch, - navigate: props.switchToDisplay, - openSeries: props.openSeries, - series: firstPage, - aimID, - existingData: filtered, - onGridFull: () => setShowSelectSeriesModal(true), - }); - return; - } - // Fell through: modality field was MG but series say otherwise — use fetched data as-is - seriesArr = filtered; - } else if (isTeachingFile) { + seriesArr = await getSeriesData(selected); + const filtered = Array.isArray(seriesArr) ? seriesArr.filter(isSupportedModality) : []; + if (isMammogramStudy(filtered)) { + props.dispatch(setMammogramSeries(filtered, studyUID)); + const firstPage = filtered.slice(0, maxPort); + setSelected(firstPage); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: props.switchToDisplay, + openSeries: props.openSeries, + series: firstPage, + aimID, + existingData: filtered, + onGridFull: () => setShowSelectSeriesModal(true), + }); + return; + } + seriesArr = filtered; + if (isTeachingFile) { seriesArr = await getSignificantSeriesData(selected); if (seriesArr.length > 0){ seriesArr = seriesArr.map( el => ({...el, patientID, studyUID, projectID, template }));} @@ -572,8 +568,6 @@ function AnnotationTable(props) { seriesArr = await getSeriesData(selected, true); seriesArr = seriesArr.slice(0,maxPort); } - } else { - seriesArr = await getSeriesData(selected); } } catch (err) { setShowSpinner(false); diff --git a/src/components/common/openSeriesHelper.js b/src/components/common/openSeriesHelper.js index 86f9d1d3c..28c44e897 100644 --- a/src/components/common/openSeriesHelper.js +++ b/src/components/common/openSeriesHelper.js @@ -13,7 +13,7 @@ import { */ export const isMammogramStudy = (seriesArray) => { console.log(" ------ Checking mammogram ------"); - if (sessionStorage.getItem('mode') !== 'lite') return false; + if (sessionStorage.getItem('mode') !== 'teaching') return false; if (!Array.isArray(seriesArray) || seriesArray.length === 0) return false; const mGVerified = seriesArray.every(s => s.examType?.toUpperCase() === 'MG'); console.log(' ---> mGVerified', mGVerified) From 0040b9ad20eb26cd2bc2adb4287bf5e45e3e34a8 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 20 Apr 2026 13:19:17 -0700 Subject: [PATCH 11/34] Add more consoles --- src/components/annotationSearch/AnnotationTable.jsx | 1 - src/components/common/openSeriesHelper.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index 84c227b70..b8a68cddb 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -555,7 +555,6 @@ function AnnotationTable(props) { }); return; } - seriesArr = filtered; if (isTeachingFile) { seriesArr = await getSignificantSeriesData(selected); if (seriesArr.length > 0){ diff --git a/src/components/common/openSeriesHelper.js b/src/components/common/openSeriesHelper.js index 28c44e897..693b22b32 100644 --- a/src/components/common/openSeriesHelper.js +++ b/src/components/common/openSeriesHelper.js @@ -12,9 +12,11 @@ import { * array is a mammogram (MG). Pass only the isSupportedModality-filtered list. */ export const isMammogramStudy = (seriesArray) => { - console.log(" ------ Checking mammogram ------"); + console.log(" ------ Checking mammogram ------", seriesArray); if (sessionStorage.getItem('mode') !== 'teaching') return false; + console.log('after mode check') if (!Array.isArray(seriesArray) || seriesArray.length === 0) return false; + console.log('after Array check') const mGVerified = seriesArray.every(s => s.examType?.toUpperCase() === 'MG'); console.log(' ---> mGVerified', mGVerified) return mGVerified; From 9c8a8c8e098e01ab3c0aa15176583331d1d7238d Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 20 Apr 2026 14:02:03 -0700 Subject: [PATCH 12/34] Fix modality support --- src/Utils/aid.js | 3 +-- src/components/annotationSearch/AnnotationTable.jsx | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Utils/aid.js b/src/Utils/aid.js index ca2b86b45..064e6d5a6 100644 --- a/src/Utils/aid.js +++ b/src/Utils/aid.js @@ -436,9 +436,8 @@ export const persistColorInDeleteAim = (oldList, newList, colorList) => { }; export const isSupportedModality = (serie) => { - // To be on the safe side do not filter if (!serie.examType) return true; - return DISP_MODALITIES.includes(serie.examType); + return DISP_MODALITIES.includes(serie.examType.toUpperCase()); }; export const getAllowedTermsOfTemplateComponent = (template, componentLabel) => { diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index b8a68cddb..589a8224b 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -539,7 +539,10 @@ function AnnotationTable(props) { try { seriesArr = await getSeriesData(selected); + console.log('seriesArr before filter'); + console.log(seriesArr); const filtered = Array.isArray(seriesArr) ? seriesArr.filter(isSupportedModality) : []; + console.log('filtered -->', filtered); if (isMammogramStudy(filtered)) { props.dispatch(setMammogramSeries(filtered, studyUID)); const firstPage = filtered.slice(0, maxPort); From e9dd9e54943d71bf19d497b7ac6dcc1258b60b26 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 20 Apr 2026 16:12:34 -0700 Subject: [PATCH 13/34] Fix mammogram toolbar and expand checkbox css --- src/components/ToolMenu/ToolMenu.jsx | 1 + src/components/display/displayView.jsx | 64 +++++++++++++------------- src/components/display/viewport.css | 9 +++- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/components/ToolMenu/ToolMenu.jsx b/src/components/ToolMenu/ToolMenu.jsx index ab55a4b1c..ed3125513 100644 --- a/src/components/ToolMenu/ToolMenu.jsx +++ b/src/components/ToolMenu/ToolMenu.jsx @@ -801,6 +801,7 @@ class ToolMenu extends Component { /> ); })} + {this.props.children} {/*
- {this.isMammogramOpen() && ( -
- - - -
- )} + > + {this.isMammogramOpen() && ( +
+ + + +
+ )} + {this.state.isLoading && (
{ e.stopPropagation(); this.handleMammoDotClick(i); }} title={this.state.selectedPorts.has(i) ? "Deselect viewport" : "Select viewport for expand"} > @@ -3323,7 +3323,7 @@ class DisplayView extends Component { )} { this.setState({ showAimEditor: true }); }} diff --git a/src/components/display/viewport.css b/src/components/display/viewport.css index 2cdc07dad..c06be1e3d 100644 --- a/src/components/display/viewport.css +++ b/src/components/display/viewport.css @@ -107,7 +107,7 @@ } */ .column.middle-right { - width: 65%; + width: 60%; display: grid; grid-template-columns: 10em 1fr; grid-template-rows: 1fr; @@ -119,9 +119,14 @@ } .column.right { - width: 15%; + width: 20%; + min-width: 48px; float: right; padding-top: 2px; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; } /* label { From 9b4ab0f67b754fa780c1a3158134cf5dc599f679 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 20 Apr 2026 19:16:23 -0700 Subject: [PATCH 14/34] Implement previous button --- src/components/display/displayView.jsx | 55 +++++++++++++++++++++----- src/components/display/viewport.css | 5 +-- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index 962d73f83..f9a371bef 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -2994,6 +2994,11 @@ class DisplayView extends Component { return (mammogramPageIndex + 1) * parseInt(maxPort) < mammogramSeries.length; }; + /** True when the user is past the first page and can go back. */ + hasPrevMammoPage = () => { + return this.props.mammogramPageIndex > 0; + }; + /** Clears the grid and loads the next group of MAMMO_PAGE_SIZE series. */ handleMammoNext = () => { const { mammogramSeries, mammogramPageIndex } = this.props; @@ -3015,6 +3020,28 @@ class DisplayView extends Component { }); }; + /** Clears the grid and loads the previous group of MAMMO_PAGE_SIZE series. */ + handleMammoPrev = () => { + const { mammogramSeries, mammogramPageIndex } = this.props; + const prevPage = mammogramPageIndex - 1; + if (prevPage < 0) return; + + const pageSize = parseInt(maxPort); + const start = prevPage * pageSize; + const prevSeries = mammogramSeries.slice(start, start + pageSize); + + this.clearMammoSelection(); + this.props.dispatch(clearGrid()); + this.props.dispatch(setMammogramPage(prevPage)); + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => {}, // already in display view + openSeries: [], // grid was just cleared + series: prevSeries, + existingData: mammogramSeries, + }); + }; + /** Toggle selection of a viewport dot. Max 2 at a time. */ handleMammoDotClick = (index) => { const { selectedPorts } = this.state; @@ -3174,6 +3201,15 @@ class DisplayView extends Component { > {this.isMammogramOpen() && (
+
+ { + this.setState({ showAimEditor: true }); + }} + > + + {this.isMammogramOpen() && ( { e.stopPropagation(); this.handleMammoDotClick(i); }} title={this.state.selectedPorts.has(i) ? "Deselect viewport" : "Select viewport for expand"} > {this.state.selectedPorts.has(i) ? : } )} - { - this.setState({ showAimEditor: true }); - }} - > - -
{data.stack && data.stack.imageIds && Date: Mon, 20 Apr 2026 19:33:17 -0700 Subject: [PATCH 15/34] Move the mamogram toolbar --- src/components/ToolMenu/ToolMenu.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ToolMenu/ToolMenu.jsx b/src/components/ToolMenu/ToolMenu.jsx index ed3125513..0885bc327 100644 --- a/src/components/ToolMenu/ToolMenu.jsx +++ b/src/components/ToolMenu/ToolMenu.jsx @@ -801,7 +801,6 @@ class ToolMenu extends Component { /> ); })} - {this.props.children} {/*
} {this.state.showMetaData && ()} {this.state.keys && ( this.setState({keys: null})} list={this.state.keys} />)} + {this.props.children}
); } From 7a228c6acd91a7f27b812aa433673c2eb40310c9 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 20 Apr 2026 20:43:36 -0700 Subject: [PATCH 16/34] Add overlay toggle bug --- src/components/display/displayView.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index f9a371bef..e9d0c3722 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -2915,7 +2915,6 @@ class DisplayView extends Component { }; toggleOverlay = (e, i) => { - if (!this.props.showingPHI && mode === 'teaching') return; const showHide = { ...this.state.isOverlayVisible }; const index = i || i === 0 ? i : this.props.activePort; if (showHide[index]) delete showHide[index]; From 243b1eb0335a06c253d10801586bf2f463d6c0a1 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 27 Apr 2026 21:24:39 -0700 Subject: [PATCH 17/34] Implement reordering modal --- src/components/ToolMenu/ToolMenu.jsx | 9 +- src/components/display/SeriesOrderModal.css | 248 +++++++++++++++ src/components/display/SeriesOrderModal.jsx | 331 ++++++++++++++++++++ src/components/display/displayView.jsx | 15 + 4 files changed, 601 insertions(+), 2 deletions(-) create mode 100644 src/components/display/SeriesOrderModal.css create mode 100644 src/components/display/SeriesOrderModal.jsx diff --git a/src/components/ToolMenu/ToolMenu.jsx b/src/components/ToolMenu/ToolMenu.jsx index 0885bc327..ae839de9a 100644 --- a/src/components/ToolMenu/ToolMenu.jsx +++ b/src/components/ToolMenu/ToolMenu.jsx @@ -43,7 +43,7 @@ import { BsArrowUpLeft } from "react-icons/bs"; import { FiSun, FiSunset, FiZoomIn, FiRotateCw } from "react-icons/fi"; import { IoMdEgg } from "react-icons/io"; import { MdLoop, MdPanTool, MdMyLocation, MdOutlineKeyboardCommandKey } from "react-icons/md"; -import { TbReplace } from "react-icons/tb"; +import { TbReplace, TbReorder } from "react-icons/tb"; import { TiDeleteOutline, TiPencil, @@ -253,6 +253,7 @@ class ToolMenu extends Component { this.managementTools = [ { name: "Save order", icon: , tool: "order", teaching: true }, { name: "Hot Keys", icon: , tool: "keys", teaching: true }, + { name: "Reorder", icon: , tool: "reorder", teaching: true }, ] this.segmentationTools = [ @@ -477,7 +478,8 @@ class ToolMenu extends Component { MetaData: true, fuse: true, order: true, - keys: true + keys: true, + reorder: true }; if (!notActiveTools[tool]) sessionStorage.setItem("activeTool", tool); @@ -577,6 +579,9 @@ class ToolMenu extends Component { } else if (tool === 'keys') { this.showHotkeyInfo(); return; + } else if (tool === 'reorder') { + if (this.props.onReorder) this.props.onReorder(); + return; } else if (tool === 'next') { this.props.openNextWLStudy(worklistID, studyUID); this.closeAllActions(); diff --git a/src/components/display/SeriesOrderModal.css b/src/components/display/SeriesOrderModal.css new file mode 100644 index 000000000..07fbdb3e5 --- /dev/null +++ b/src/components/display/SeriesOrderModal.css @@ -0,0 +1,248 @@ +/* ── Bootstrap Modal overrides ─────────────────────────────── */ +.series-order-modal .modal-content, +.som-warn-modal .modal-content { + background-color: #1a2035; + border: 1px solid #2e3a50; + color: #d0d8e8; +} + +.series-order-modal .modal-header, +.som-warn-modal .modal-header { + background-color: #1a2035; + border-bottom: 1px solid #2e3a50; + color: #d0d8e8; + display: flex; + align-items: center; + justify-content: space-between; +} + +.series-order-modal .modal-title, +.som-warn-modal .modal-title { + color: #d0d8e8; + font-size: 1rem; +} + +.series-order-modal .modal-header .btn-close, +.som-warn-modal .modal-header .btn-close { + filter: invert(1) grayscale(1) brightness(1.5); + opacity: 0.7; +} + +.series-order-modal .modal-header .btn-close:hover, +.som-warn-modal .modal-header .btn-close:hover { + opacity: 1; +} + +.series-order-modal .modal-body, +.som-warn-modal .modal-body { + background-color: #1a2035; + color: #d0d8e8; +} + +.series-order-modal .modal-footer, +.som-warn-modal .modal-footer { + background-color: #1a2035; + border-top: 1px solid #2e3a50; +} + +/* ── Loading ──────────────────────────────────────────────── */ +.som-loading { + text-align: center; + padding: 2rem; + color: #aaa; +} + +/* ── Grid + actions row ───────────────────────────────────── */ +.som-grid-row { + display: flex; + align-items: flex-start; + gap: 16px; + margin-bottom: 12px; +} + +.som-grid { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr 1fr; + gap: 8px; + flex: 0 0 auto; + width: 360px; +} + +.som-slot { + height: 72px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + border: 2px dashed #3a4a5e; + transition: border-color 0.15s; +} + +.som-slot--empty { + background: #151d2e; +} + +.som-slot--filled { + border-style: solid; + border-color: #4a7ab5; + background: #1e2a3a; +} + +.som-slot--droptarget:hover { + border-color: #aadcff; +} + +.som-slot-number { + font-size: 1.4rem; + color: #3a4a5e; + font-weight: bold; +} + +/* ── Series chip ──────────────────────────────────────────── */ +.som-chip { + display: inline-block; + background: #2d4a6e; + color: #cce0ff; + border-radius: 3px; + padding: 4px 8px; + font-size: 0.78rem; + cursor: grab; + max-width: 160px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + user-select: none; +} + +.som-chip:active { + cursor: grabbing; +} + +.som-chip--list { + margin: 4px; + max-width: 100%; +} + +/* ── Grid action buttons ──────────────────────────────────── */ +.som-grid-actions { + display: flex; + flex-direction: column; + gap: 8px; + padding-top: 4px; +} + +/* ── Page navigation ──────────────────────────────────────── */ +.som-pagination { + display: flex; + align-items: center; + gap: 4px; + margin-bottom: 12px; +} + +.som-page-nav, +.som-page-num { + background: #222c3e; + border: 1px solid #3a4a5e; + color: #aab8cc; + border-radius: 3px; + padding: 2px 8px; + cursor: pointer; + font-size: 0.85rem; + transition: background 0.15s; +} + +.som-page-nav:disabled { + opacity: 0.3; + cursor: not-allowed; +} + +.som-page-nav:not(:disabled):hover, +.som-page-num:hover { + background: #4a7ab5; + color: #fff; +} + +.som-page-num--active { + background: #4a7ab5; + color: #fff; + border-color: #4a7ab5; +} + +/* ── Separator between grid area and unordered panel ─────── */ +.som-divider { + border: none; + border-top: 1px solid #2e3a50; + margin: 12px 0; +} + +/* ── Unordered list ───────────────────────────────────────── */ +.som-unordered { + border: 1px solid #2e3a50; + border-radius: 4px; + min-height: 80px; + padding: 8px; + background: #151d2e; +} + +.som-unordered-label { + font-size: 0.85rem; + color: #5a6a80; + margin-bottom: 8px; +} + +.som-unordered-empty { + font-size: 0.85rem; + color: #4a5a6e; + padding: 8px 0; +} + +/* ── Buttons ──────────────────────────────────────────────── */ +.som-btn { + padding: 6px 16px; + border-radius: 3px; + border: none; + cursor: pointer; + font-size: 0.85rem; + transition: opacity 0.15s, background 0.15s; +} + +.som-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.som-btn--primary { + background: #4a7ab5; + color: #fff; +} + +.som-btn--primary:hover:not(:disabled) { + background: #5a8ac5; +} + +.som-btn--secondary { + background: #1e2a3a; + color: #aab8cc; + border: 1px solid #3a4a5e; +} + +.som-btn--secondary:hover:not(:disabled) { + background: #283a52; +} + +/* ── Warning modal body text ──────────────────────────────── */ +.som-warn-modal .modal-body p { + color: #c0cce0; + font-size: 0.9rem; +} + +.som-warn-check { + display: flex; + align-items: center; + font-size: 0.85rem; + color: #aab8cc; + cursor: pointer; + margin-top: 8px; + gap: 6px; +} diff --git a/src/components/display/SeriesOrderModal.jsx b/src/components/display/SeriesOrderModal.jsx new file mode 100644 index 000000000..31c4eaea6 --- /dev/null +++ b/src/components/display/SeriesOrderModal.jsx @@ -0,0 +1,331 @@ +import React, { useState, useEffect, useRef } from 'react'; +import Modal from 'react-bootstrap/Modal'; +import { toast } from 'react-toastify'; +import { getSeries } from '../../services/seriesServices'; +import { setSignificantSeries } from '../../services/seriesServices'; +import './SeriesOrderModal.css'; + +const SLOTS = 4; +const WARN_KEY = 'noSeriesStateWarn'; + +const hasDisplayState = (serie) => + serie.displayState && + Object.values(serie.displayState).some(v => v !== null && v !== '' && v !== undefined); + +export default function SeriesOrderModal({ show, onClose, projectID, subjectUID, studyUID }) { + const [pages, setPages] = useState([Array(SLOTS).fill(null)]); + const [unordered, setUnordered] = useState([]); + const [currentPage, setCurrentPage] = useState(0); + const [loading, setLoading] = useState(false); + const [dragSource, setDragSource] = useState(null); + const [showStateWarn, setShowStateWarn] = useState(false); + const [dontShowAgain, setDontShowAgain] = useState(false); + const [showEmptyWarn, setShowEmptyWarn] = useState(false); + const [emptyPages, setEmptyPages] = useState([]); + const [pendingSavePages, setPendingSavePages] = useState(null); + const stateWarnShown = useRef(false); + const initialWithState = useRef(new Set()); + + useEffect(() => { + if (show) { + stateWarnShown.current = false; + initialWithState.current = new Set(); + setCurrentPage(0); + setShowStateWarn(false); + setShowEmptyWarn(false); + setPendingSavePages(null); + loadData(); + } + }, [show]); + + const loadData = async () => { + setLoading(true); + try { + const { data: seriesArr } = await getSeries(projectID, subjectUID, studyUID); + + const ordered = seriesArr.filter(s => s.significanceOrder != null); + const unorderedArr = seriesArr.filter(s => s.significanceOrder == null); + + // Track which initially-ordered series have a displayState + ordered.forEach(s => { if (hasDisplayState(s)) initialWithState.current.add(s.seriesUID); }); + + // Build page grid + const pageMap = {}; + ordered.forEach(s => { + const pageIdx = (s.pageOrder != null ? s.pageOrder : 1) - 1; + const slotIdx = (s.significanceOrder || 1) - 1; + if (!pageMap[pageIdx]) pageMap[pageIdx] = Array(SLOTS).fill(null); + pageMap[pageIdx][Math.min(slotIdx, SLOTS - 1)] = s; + }); + + const maxPage = ordered.length > 0 ? Math.max(...Object.keys(pageMap).map(Number)) : 0; + const pagesArr = []; + for (let i = 0; i <= maxPage; i++) { + pagesArr.push(pageMap[i] || Array(SLOTS).fill(null)); + } + + setPages(pagesArr); + setUnordered(unorderedArr); + } catch (err) { + toast.error('Could not load series data'); + } finally { + setLoading(false); + } + }; + + // --- Drag helpers --- + + const handleDragStart = (source) => setDragSource(source); + + const handleDropOnSlot = (targetSlot) => { + if (!dragSource) return; + const newPages = pages.map(p => [...p]); + const newUnordered = [...unordered]; + let draggedSerie; + + if (dragSource.type === 'slot') { + draggedSerie = newPages[dragSource.page][dragSource.slot]; + newPages[dragSource.page][dragSource.slot] = null; + } else { + draggedSerie = newUnordered.splice(dragSource.index, 1)[0]; + } + + const displaced = newPages[currentPage][targetSlot]; + newPages[currentPage][targetSlot] = draggedSerie; + + if (displaced) { + if (dragSource.type === 'slot') { + newPages[dragSource.page][dragSource.slot] = displaced; + } else { + newUnordered.push(displaced); + maybeShowStateWarn(displaced); + } + } + + setPages(newPages); + setUnordered(newUnordered); + setDragSource(null); + }; + + const handleDropOnList = () => { + if (!dragSource || dragSource.type === 'list') return; + const newPages = pages.map(p => [...p]); + const draggedSerie = newPages[dragSource.page][dragSource.slot]; + if (!draggedSerie) return; + + newPages[dragSource.page][dragSource.slot] = null; + maybeShowStateWarn(draggedSerie); + setPages(newPages); + setUnordered(prev => [...prev, draggedSerie]); + setDragSource(null); + }; + + const maybeShowStateWarn = (serie) => { + if (stateWarnShown.current) return; + if (localStorage.getItem(WARN_KEY) === 'true') return; + if (initialWithState.current.has(serie.seriesUID)) { + setShowStateWarn(true); + stateWarnShown.current = true; + } + }; + + // --- Grid actions --- + + const handleClearGrid = () => { + const newPages = pages.map(p => [...p]); + const cleared = newPages[currentPage].filter(Boolean); + newPages[currentPage] = Array(SLOTS).fill(null); + cleared.forEach(maybeShowStateWarn); + setPages(newPages); + setUnordered(prev => [...prev, ...cleared]); + }; + + const handleAddPage = () => { + setPages(prev => [...prev, Array(SLOTS).fill(null)]); + setCurrentPage(pages.length); + }; + + // --- Save --- + + const handleSave = () => { + // Find empty pages sandwiched between non-empty pages + const nonEmptyIndices = pages.reduce((acc, p, i) => { if (p.some(Boolean)) acc.push(i); return acc; }, []); + if (nonEmptyIndices.length > 1) { + const first = nonEmptyIndices[0]; + const last = nonEmptyIndices[nonEmptyIndices.length - 1]; + const emptyBetween = []; + for (let i = first + 1; i < last; i++) { + if (!pages[i].some(Boolean)) emptyBetween.push(i + 1); + } + if (emptyBetween.length > 0) { + setEmptyPages(emptyBetween); + setPendingSavePages(pages); + setShowEmptyWarn(true); + return; + } + } + doSave(pages); + }; + + const doSave = (pagesArr) => { + const nonEmptyPages = pagesArr.filter(p => p.some(Boolean)); + const payload = []; + nonEmptyPages.forEach((page, pageIdx) => { + page.forEach((serie, slotIdx) => { + if (!serie) return; + const record = { seriesUID: serie.seriesUID, significanceOrder: slotIdx + 1, pageOrder: pageIdx + 1 }; + if (hasDisplayState(serie)) record.displayState = serie.displayState; + payload.push(record); + }); + }); + setSignificantSeries(projectID, subjectUID, studyUID, payload, true) + .then(() => { toast.success('Series order saved!'); onClose(); }) + .catch(() => toast.error('Could not save series order')); + }; + + // --- Render helpers --- + + const seriesLabel = (serie) => + `${serie.seriesNo != null ? serie.seriesNo : '–'} · ${serie.seriesDescription || serie.examType || ''}`; + + const currentSlots = pages[currentPage] || Array(SLOTS).fill(null); + + return ( + <> + + + Series Display Order + + + {loading ? ( +
Loading series…
+ ) : ( + <> + {/* Grid + action buttons */} +
+
+ {currentSlots.map((serie, slotIdx) => ( +
e.preventDefault()} + onDrop={() => handleDropOnSlot(slotIdx)} + > + {serie ? ( + handleDragStart({ type: 'slot', page: currentPage, slot: slotIdx, serie })} + > + {seriesLabel(serie)} + + ) : ( + {slotIdx + 1} + )} +
+ ))} +
+
+ + +
+
+ + {/* Page navigation */} +
+ + {pages.map((_, i) => ( + + ))} + +
+ +
+ + {/* Unordered list */} +
e.preventDefault()} + onDrop={handleDropOnList} + > +
── Unordered Series ──
+ {unordered.length === 0 &&
All series are assigned to pages.
} + {unordered.map((serie, idx) => ( + handleDragStart({ type: 'list', index: idx, serie })} + > + {seriesLabel(serie)} + + ))} +
+ + )} +
+ + + + +
+ + {/* Display state loss warning */} + {showStateWarn && ( + setShowStateWarn(false)} size="sm" className="som-warn-modal"> + + ⚠ Saved State Will Be Lost + + +

Moving a series out of the order permanently removes its saved display state (window/level, zoom, invert). This cannot be undone.

+ +
+ + + +
+ )} + + {/* Empty page warning */} + {showEmptyWarn && ( + setShowEmptyWarn(false)} size="sm" className="som-warn-modal"> + + ⚠ Page {emptyPages[0]} is Empty + + +

+ Page {emptyPages[0]} has no series assigned. If you continue, pages will be renumbered: + Page {emptyPages[0] + 1} will become Page {emptyPages[0]}. +

+
+ + + + +
+ )} + + ); +} diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index e9d0c3722..cd61c2328 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -55,6 +55,7 @@ import "./viewport.css"; import { toast } from "react-toastify"; import SeriesDropDown from "./SeriesDropDown"; import getVPDimensions from "./ViewportCalculations"; +import SeriesOrderModal from "./SeriesOrderModal"; let mode; let wadoUrl; @@ -210,6 +211,7 @@ class DisplayView extends Component { isVisible: true, selectedPorts: new Set(), mammoExpanded: false, + showReorderModal: false, }; } @@ -3197,6 +3199,7 @@ class DisplayView extends Component { onFuseNewImage={this.newImageFuse} onOpenSeries={this.props.openSeries} openNextWLStudy={this.openNextWLStudy} + onReorder={() => this.setState({ showReorderModal: true })} > {this.isMammogramOpen() && (
@@ -3416,6 +3419,18 @@ class DisplayView extends Component { closeViewport={this.closeViewport} /> */} + {this.state.showReorderModal && (() => { + const active = this.props.series[this.props.activePort] || {}; + return ( + this.setState({ showReorderModal: false })} + projectID={active.projectID} + subjectUID={active.patientID} + studyUID={active.studyUID} + /> + ); + })()} ); //
From 5b18602bb9f06914d091b0d1adc5bdb4e9e1d88b Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 27 Apr 2026 21:59:12 -0700 Subject: [PATCH 18/34] Enable new toolbar for all modalities --- src/components/display/displayView.jsx | 74 +++++++++++++------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index cd61c2328..8af29f6e4 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -3201,44 +3201,42 @@ class DisplayView extends Component { openNextWLStudy={this.openNextWLStudy} onReorder={() => this.setState({ showReorderModal: true })} > - {this.isMammogramOpen() && ( -
- - - - -
- )} +
+ + + + +
{this.state.isLoading && (
From a3f2a04dea51b84209f9d64d076b2bee0b9b3893 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Mon, 27 Apr 2026 22:17:23 -0700 Subject: [PATCH 19/34] Display sig series based on pageOrder - Opening TF from search - Openning from flexview --- .../annotationSearch/AnnotationTable.jsx | 34 +++++++++++++++---- .../annotationsList/selectSerieModal.jsx | 20 +++++------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index 589a8224b..216b7ddad 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -558,17 +558,39 @@ function AnnotationTable(props) { }); return; } + // Saved ordering: load page-1 significant series directly, never show modal. + const significant = filtered.filter(s => s.significanceOrder != null); + if (significant.length > 0) { + const hasPageOrder = significant.some(s => s.pageOrder != null); + let toDisplay = hasPageOrder + ? significant.filter(s => s.pageOrder === 1) // new ordering: page 1 only + : significant; // legacy: all significant (≤4) + if (toDisplay.length > 0) { + toDisplay = toDisplay.sort((a, b) => (a.significanceOrder || 0) - (b.significanceOrder || 0)); + setSelected(toDisplay); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: props.switchToDisplay, + openSeries: props.openSeries, + series: toDisplay, + aimID, + existingData: getExistingData(selected), + }); + return; + } + } + if (isTeachingFile) { - seriesArr = await getSignificantSeriesData(selected); - if (seriesArr.length > 0){ - seriesArr = seriesArr.map( el => ({...el, patientID, studyUID, projectID, template }));} - else if (existingData && existingData.length <= maxPort) { + seriesArr = await getSignificantSeriesData(selected); + if (seriesArr.length > 0) { + seriesArr = seriesArr.map(el => ({ ...el, patientID, studyUID, projectID, template })); + } else if (existingData && existingData.length <= maxPort) { seriesArr = existingData; } else if (existingData && existingData.length > maxPort) { - seriesArr = existingData.slice(0,maxPort); + seriesArr = existingData.slice(0, maxPort); } else { seriesArr = await getSeriesData(selected, true); - seriesArr = seriesArr.slice(0,maxPort); + seriesArr = seriesArr.slice(0, maxPort); } } } catch (err) { diff --git a/src/components/annotationsList/selectSerieModal.jsx b/src/components/annotationsList/selectSerieModal.jsx index b85211581..9b060b1d3 100644 --- a/src/components/annotationsList/selectSerieModal.jsx +++ b/src/components/annotationsList/selectSerieModal.jsx @@ -278,6 +278,11 @@ class selectSerieModal extends React.Component { let selectedCount = 0; let series = Object.values(seriesPassed); let count = 0; + + // If any series carry pageOrder, only pre-select page 1 series. + const allSeries = series.flat(); + const hasPageOrder = allSeries.some(s => s.pageOrder != null); + for (let i = 0; i < series.length; i++) { for (let k = 0; k < series[i].length; k++) { if (openSeries.length + selectedCount >= this.maxPort) { @@ -286,17 +291,10 @@ class selectSerieModal extends React.Component { }); return; } - // if (!this.isSerieOpen(series[i][k].seriesUID)) { - // selectedToDisplay[series[i][k].seriesUID] = series[i][k].significanceOrder - // ? true - // : false; - // selectedCount++; - // } - if ( - series[i][k].significanceOrder && - !this.isSerieOpen(series[i][k].seriesUID) - ) { - selectedToDisplay[series[i][k].seriesUID] = true; + const s = series[i][k]; + const isPageOneOrLegacy = !hasPageOrder || s.pageOrder === 1; + if (s.significanceOrder && !this.isSerieOpen(s.seriesUID) && isPageOneOrLegacy) { + selectedToDisplay[s.seriesUID] = true; selectedCount++; } } From 2515ee602e60c75bf4679cb3482b00df2cdb33a8 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Tue, 28 Apr 2026 14:52:11 -0700 Subject: [PATCH 20/34] Integrte NEXT PREV button to pageorder logic --- .../annotationSearch/AnnotationTable.jsx | 70 +++++++++++------ src/components/annotationsList/action.js | 17 +++++ src/components/annotationsList/reducer.js | 11 +++ src/components/annotationsList/types.js | 3 + src/components/display/displayView.jsx | 75 +++++++++++++++++-- src/components/sideBar/sideBarWorklist.jsx | 44 ++++++++++- 6 files changed, 189 insertions(+), 31 deletions(-) diff --git a/src/components/annotationSearch/AnnotationTable.jsx b/src/components/annotationSearch/AnnotationTable.jsx index 216b7ddad..d506c35f3 100644 --- a/src/components/annotationSearch/AnnotationTable.jsx +++ b/src/components/annotationSearch/AnnotationTable.jsx @@ -20,6 +20,8 @@ import { setSeriesData, storeAimSelection, setMammogramSeries, + setPageOrderSeries, + clearPageOrderSeries, } from "../annotationsList/action"; import { openSeriesInDisplay, isMammogramStudy } from "../common/openSeriesHelper"; import { formatDate } from "../flexView/helperMethods"; @@ -543,41 +545,63 @@ function AnnotationTable(props) { console.log(seriesArr); const filtered = Array.isArray(seriesArr) ? seriesArr.filter(isSupportedModality) : []; console.log('filtered -->', filtered); + + // Decision tree: significant series take priority over mammogram fallback. + const significant = filtered.filter(s => s.significanceOrder != null); + const hasPageOrder = significant.length > 0 && significant.some(s => s.pageOrder != null); + + if (significant.length > 0 && hasPageOrder) { + // Case 1: pageOrder navigation — load page 1, enable Next/Prev by pageOrder. + props.dispatch(setPageOrderSeries(significant)); + let toDisplay = significant + .filter(s => s.pageOrder === 1) + .sort((a, b) => (a.significanceOrder || 0) - (b.significanceOrder || 0)); + if (toDisplay.length === 0) toDisplay = significant.slice(0, maxPort); + setSelected(toDisplay); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: props.switchToDisplay, + openSeries: props.openSeries, + series: toDisplay, + aimID, + existingData: filtered, + }); + return; + } + if (isMammogramStudy(filtered)) { + // Case 3 (no significant) or Case 6 (significant, no pageOrder, MG). + props.dispatch(clearPageOrderSeries()); props.dispatch(setMammogramSeries(filtered, studyUID)); - const firstPage = filtered.slice(0, maxPort); - setSelected(firstPage); + const toDisplay = significant.length > 0 + ? significant.sort((a, b) => (a.significanceOrder || 0) - (b.significanceOrder || 0)) // Case 6 + : filtered.slice(0, maxPort); // Case 3 + setSelected(toDisplay); openSeriesInDisplay({ dispatch: props.dispatch, navigate: props.switchToDisplay, openSeries: props.openSeries, - series: firstPage, + series: toDisplay, aimID, existingData: filtered, - onGridFull: () => setShowSelectSeriesModal(true), }); return; } - // Saved ordering: load page-1 significant series directly, never show modal. - const significant = filtered.filter(s => s.significanceOrder != null); + if (significant.length > 0) { - const hasPageOrder = significant.some(s => s.pageOrder != null); - let toDisplay = hasPageOrder - ? significant.filter(s => s.pageOrder === 1) // new ordering: page 1 only - : significant; // legacy: all significant (≤4) - if (toDisplay.length > 0) { - toDisplay = toDisplay.sort((a, b) => (a.significanceOrder || 0) - (b.significanceOrder || 0)); - setSelected(toDisplay); - openSeriesInDisplay({ - dispatch: props.dispatch, - navigate: props.switchToDisplay, - openSeries: props.openSeries, - series: toDisplay, - aimID, - existingData: getExistingData(selected), - }); - return; - } + // Case 2: significant series, no pageOrder, non-mammogram — load directly, no Next/Prev. + props.dispatch(clearPageOrderSeries()); + const toDisplay = significant.sort((a, b) => (a.significanceOrder || 0) - (b.significanceOrder || 0)); + setSelected(toDisplay); + openSeriesInDisplay({ + dispatch: props.dispatch, + navigate: props.switchToDisplay, + openSeries: props.openSeries, + series: toDisplay, + aimID, + existingData: getExistingData(selected), + }); + return; } if (isTeachingFile) { diff --git a/src/components/annotationsList/action.js b/src/components/annotationsList/action.js index 3bbf9909e..b42b5deb2 100644 --- a/src/components/annotationsList/action.js +++ b/src/components/annotationsList/action.js @@ -59,6 +59,9 @@ import { SET_MAMMOGRAM_SERIES, SET_MAMMOGRAM_PAGE, CLEAR_MAMMOGRAM_SERIES, + SET_PAGE_ORDER_SERIES, + SET_PAGE_ORDER, + CLEAR_PAGE_ORDER_SERIES, colors, commonLabels, } from "./types"; @@ -1219,6 +1222,20 @@ export const clearMammogramSeries = () => ({ type: CLEAR_MAMMOGRAM_SERIES, }); +export const setPageOrderSeries = (allSeries) => ({ + type: SET_PAGE_ORDER_SERIES, + payload: allSeries, +}); + +export const setPageOrder = (pageOrder) => ({ + type: SET_PAGE_ORDER, + payload: pageOrder, +}); + +export const clearPageOrderSeries = () => ({ + type: CLEAR_PAGE_ORDER_SERIES, +}); + export const otherAimsUpdated = (seriesList, aimRefs) => { return { type: AIM_SAVE, payload: { seriesList, aimRefs } }; diff --git a/src/components/annotationsList/reducer.js b/src/components/annotationsList/reducer.js index 9c6e4dbbf..553088efc 100644 --- a/src/components/annotationsList/reducer.js +++ b/src/components/annotationsList/reducer.js @@ -60,6 +60,9 @@ import { SET_MAMMOGRAM_SERIES, SET_MAMMOGRAM_PAGE, CLEAR_MAMMOGRAM_SERIES, + SET_PAGE_ORDER_SERIES, + SET_PAGE_ORDER, + CLEAR_PAGE_ORDER_SERIES, colors, commonLabels, } from "./types"; @@ -118,6 +121,8 @@ const initialState = { mammogramSeries: [], mammogramStudyUID: null, mammogramPageIndex: 0, + pageOrderSeries: [], + currentPageOrder: 1, }; @@ -1048,6 +1053,12 @@ const asyncReducer = (state = initialState, action) => { return { ...state, mammogramPageIndex: action.payload }; case CLEAR_MAMMOGRAM_SERIES: return { ...state, mammogramSeries: [], mammogramStudyUID: null, mammogramPageIndex: 0 }; + case SET_PAGE_ORDER_SERIES: + return { ...state, pageOrderSeries: action.payload, currentPageOrder: 1 }; + case SET_PAGE_ORDER: + return { ...state, currentPageOrder: action.payload }; + case CLEAR_PAGE_ORDER_SERIES: + return { ...state, pageOrderSeries: [], currentPageOrder: 1 }; default: return state; } diff --git a/src/components/annotationsList/types.js b/src/components/annotationsList/types.js index 1a5a08b75..9a5c89f72 100644 --- a/src/components/annotationsList/types.js +++ b/src/components/annotationsList/types.js @@ -72,6 +72,9 @@ export const SHOW_PHI = 'epadjs/annotationList/SHOW_PHI'; export const SET_MAMMOGRAM_SERIES = 'epadjs/annotationList/SET_MAMMOGRAM_SERIES'; export const SET_MAMMOGRAM_PAGE = 'epadjs/annotationList/SET_MAMMOGRAM_PAGE'; export const CLEAR_MAMMOGRAM_SERIES = 'epadjs/annotationList/CLEAR_MAMMOGRAM_SERIES'; +export const SET_PAGE_ORDER_SERIES = 'epadjs/annotationList/SET_PAGE_ORDER_SERIES'; +export const SET_PAGE_ORDER = 'epadjs/annotationList/SET_PAGE_ORDER'; +export const CLEAR_PAGE_ORDER_SERIES = 'epadjs/annotationList/CLEAR_PAGE_ORDER_SERIES'; export const commonLabels = { button: { background: "#6c757d", color: "#ECECEC", border: "#acacac solid 1px" }, diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index 8af29f6e4..c9eb000a4 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -37,6 +37,7 @@ import { setSegLabelMapIndex, setSeriesData, setMammogramPage, + setPageOrder, // fillSeriesDescfullData updateGridWithMultiFrameInfo, updateImageId, @@ -167,6 +168,8 @@ const mapStateToProps = (state) => { showingPHI: state.annotationsListReducer.showingPHI, mammogramSeries: state.annotationsListReducer.mammogramSeries, mammogramPageIndex: state.annotationsListReducer.mammogramPageIndex, + pageOrderSeries: state.annotationsListReducer.pageOrderSeries, + currentPageOrder: state.annotationsListReducer.currentPageOrder, }; }; @@ -1111,9 +1114,10 @@ class DisplayView extends Component { indexKey = `${indexKey}-${mfIndexFinal}` } - const dataExistsInState = parseInt(dataIndexMap[indexKey]) >= 0; + const cachedIdx = parseInt(dataIndexMap[indexKey]); + const dataExistsInState = cachedIdx >= 0 && cachedIdx < this.state.data.length && !!this.state.data[cachedIdx]; - if (!dataExistsInState || force) { + if (!dataExistsInState || force) { const promise = this.getImageStack( series[i], i, @@ -2989,17 +2993,76 @@ class DisplayView extends Component { return hasMammoSeries && hasOpenMG; }; + /** True when pageOrder navigation is active (Case 1). */ + isPageOrderNav = () => this.props.pageOrderSeries && this.props.pageOrderSeries.length > 0; + /** True when there is at least one more page of mammogram series to load. */ hasNextMammoPage = () => { + if (this.isPageOrderNav()) return false; const { mammogramSeries, mammogramPageIndex } = this.props; return (mammogramPageIndex + 1) * parseInt(maxPort) < mammogramSeries.length; }; /** True when the user is past the first page and can go back. */ hasPrevMammoPage = () => { + if (this.isPageOrderNav()) return false; return this.props.mammogramPageIndex > 0; }; + /** True when there is a higher pageOrder page available. */ + hasNextPageOrderPage = () => { + const { pageOrderSeries, currentPageOrder } = this.props; + return pageOrderSeries.some(s => s.pageOrder === currentPageOrder + 1); + }; + + /** True when the user is past page 1 in pageOrder navigation. */ + hasPrevPageOrderPage = () => { + return this.props.currentPageOrder > 1; + }; + + /** Clears the grid and loads the next pageOrder page. */ + handlePageOrderNext = () => { + const { pageOrderSeries, currentPageOrder } = this.props; + const nextPage = currentPageOrder + 1; + const nextSeries = pageOrderSeries + .filter(s => s.pageOrder === nextPage) + .sort((a, b) => (a.significanceOrder || 0) - (b.significanceOrder || 0)); + if (!nextSeries.length) return; + + this.clearMammoSelection(); + this.props.dispatch(clearGrid()); + this.props.dispatch(setPageOrder(nextPage)); + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => {}, + openSeries: [], + series: nextSeries, + existingData: pageOrderSeries, + }); + }; + + /** Clears the grid and loads the previous pageOrder page. */ + handlePageOrderPrev = () => { + const { pageOrderSeries, currentPageOrder } = this.props; + const prevPage = currentPageOrder - 1; + if (prevPage < 1) return; + + const prevSeries = pageOrderSeries + .filter(s => s.pageOrder === prevPage) + .sort((a, b) => (a.significanceOrder || 0) - (b.significanceOrder || 0)); + + this.clearMammoSelection(); + this.props.dispatch(clearGrid()); + this.props.dispatch(setPageOrder(prevPage)); + openSeriesInDisplay({ + dispatch: this.props.dispatch, + navigate: () => {}, + openSeries: [], + series: prevSeries, + existingData: pageOrderSeries, + }); + }; + /** Clears the grid and loads the next group of MAMMO_PAGE_SIZE series. */ handleMammoNext = () => { const { mammogramSeries, mammogramPageIndex } = this.props; @@ -3204,8 +3267,8 @@ class DisplayView extends Component {
- + */}
{this.state.isLoading && ( @@ -3492,6 +3683,30 @@ class DisplayView extends Component { /> ); })()} + {this.state.showSaveStatusWarning && ( +
+
+
⚠ Series Will Be Added to Significant Series
+

+ One or more series with saved states are not yet in your display order. Saving will add them as significant series. +

+
+ + +
+
+
+ )} ); //
From c8bd285258dd92bf045bc6b792be3bbe9f568e10 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Sun, 24 May 2026 15:17:45 -0700 Subject: [PATCH 23/34] Show expand checkbox if the modality is MG --- src/components/display/displayView.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index e1b1b1e95..a7f73771d 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -3000,7 +3000,6 @@ class DisplayView extends Component { const { series, mammogramSeries } = this.props; const hasMammoSeries = !!(mammogramSeries && mammogramSeries.length > 0); const hasOpenMG = !!(series && series.some(s => s && s.examType?.toUpperCase() === 'MG')); - console.log('[MG Debug] isMammogramOpen — mammogramSeries.length:', mammogramSeries?.length, '| hasOpenMG:', hasOpenMG, '| result:', hasMammoSeries && hasOpenMG); return hasMammoSeries && hasOpenMG; }; @@ -3611,7 +3610,7 @@ class DisplayView extends Component { > - {this.isMammogramOpen() && ( + {this.props.series && this.props.series[i] && this.props.series[i].examType?.toUpperCase() === 'MG' && ( Date: Sun, 24 May 2026 21:16:39 -0700 Subject: [PATCH 24/34] Expand viewports with the selection order --- src/components/display/displayView.jsx | 35 +++++++++++--------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index a7f73771d..df3620dec 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -215,6 +215,8 @@ class DisplayView extends Component { isVisible: true, selectedPorts: new Set(), mammoExpanded: false, + hiddenPorts: new Set(), + expandedOrder: null, showReorderModal: false, showSaveStatusWarning: false, pendingSaveStatusData: null, @@ -3152,7 +3154,7 @@ class DisplayView extends Component { */ handleMammoExpand = () => { const { activePort } = this.props; - const { selectedPorts, mammoExpanded, containerHeight } = this.state; + const { selectedPorts, mammoExpanded, containerHeight, data } = this.state; if (mammoExpanded) { this.restoreMammoExpand(); @@ -3160,13 +3162,11 @@ class DisplayView extends Component { } if (selectedPorts.size === 2) { + // selectedArr preserves insertion order: [firstSelected, secondSelected] const selectedArr = Array.from(selectedPorts); - const elements = document.getElementsByClassName("viewportContainer"); - for (let i = 0; i < elements.length; i++) { - elements[i].style.display = selectedArr.includes(i) ? "inline-block" : "none"; - } + const hidden = new Set(data.map((_, i) => i).filter(i => !selectedArr.includes(i))); this.setState( - { mammoExpanded: true, width: "50%", height: containerHeight }, + { mammoExpanded: true, width: "50%", height: containerHeight, hiddenPorts: hidden, expandedOrder: selectedArr }, () => window.dispatchEvent(new CustomEvent("resize", { detail: { isMaximize: true } })) ); } else { @@ -3176,12 +3176,8 @@ class DisplayView extends Component { /** Restore all viewports to the standard grid layout and clear dot selections. */ restoreMammoExpand = () => { - const elements = document.getElementsByClassName("viewportContainer"); - for (let i = 0; i < elements.length; i++) { - elements[i].style.display = "inline-block"; - } this.setState( - { mammoExpanded: false, selectedPorts: new Set() }, + { mammoExpanded: false, selectedPorts: new Set(), hiddenPorts: new Set(), expandedOrder: null }, () => { this.getViewports(); window.dispatchEvent(new CustomEvent("resize", { detail: { isMaximize: false } })); @@ -3191,14 +3187,7 @@ class DisplayView extends Component { /** Clear mammogram selection state (dots + expand). Called on NEXT and new study. */ clearMammoSelection = () => { - if (this.state.mammoExpanded) { - // Restore hidden viewports before clearing state - const elements = document.getElementsByClassName("viewportContainer"); - for (let i = 0; i < elements.length; i++) { - elements[i].style.display = "inline-block"; - } - } - this.setState({ selectedPorts: new Set(), mammoExpanded: false }); + this.setState({ selectedPorts: new Set(), mammoExpanded: false, hiddenPorts: new Set(), expandedOrder: null }); }; // --- End mammogram pagination --- @@ -3502,7 +3491,9 @@ class DisplayView extends Component { )} {!this.state.isLoading && Object.entries(series).length && - data.map((data, i) => { +
+ {data.map((data, i) => { + const { hiddenPorts, expandedOrder } = this.state; return (
this.setActive(i)} > @@ -3665,6 +3657,7 @@ class DisplayView extends Component {
); })} +
} {/* Date: Sun, 24 May 2026 22:35:53 -0700 Subject: [PATCH 25/34] Set maxport limit to 8 for MG modality --- src/components/annotationsList/selectSerieModal.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/annotationsList/selectSerieModal.jsx b/src/components/annotationsList/selectSerieModal.jsx index 9b060b1d3..038aa0c89 100644 --- a/src/components/annotationsList/selectSerieModal.jsx +++ b/src/components/annotationsList/selectSerieModal.jsx @@ -65,6 +65,16 @@ class selectSerieModal extends React.Component { selectionType = "aim"; } this.setState({ selectionType }); + + // For mammogram studies (all series have examType MG), allow up to 8 significant series + const allSeriesFlat = (Array.isArray(this.props.seriesPassed) + ? this.props.seriesPassed + : Object.values(this.props.seriesPassed) + ).flat(); + if (allSeriesFlat.length > 0 && allSeriesFlat.every(s => (s.examType || s.modality)?.toUpperCase() === 'MG')) { + this.maxPort = 8; + } + this.setPreSelecteds(); const limit = this.updateLimit(); this.setState({ limit }); From cd871fbaf4550e839002c00df6aac6ca65ea70d7 Mon Sep 17 00:00:00 2001 From: OzgeYurtsever Date: Tue, 26 May 2026 12:43:37 -0700 Subject: [PATCH 26/34] Fix MG detection and series order distribution - selectSerieModal: exclude PR files before MG check so mammogram studies with presentation state series correctly get maxPort=8 - displayView: fall back to modality field when examType is absent for MG checkbox visibility and isMammogramOpen check - SeriesOrderModal: replace fixed slot cap with page-wrap logic so series with significanceOrder > SLOTS distribute across pages instead of overwriting each other; remove dynamic slots state Co-Authored-By: Claude Sonnet 4.6 --- src/components/annotationsList/selectSerieModal.jsx | 7 +++++-- src/components/display/SeriesOrderModal.jsx | 9 ++++++--- src/components/display/displayView.jsx | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/annotationsList/selectSerieModal.jsx b/src/components/annotationsList/selectSerieModal.jsx index 038aa0c89..ec5a0648f 100644 --- a/src/components/annotationsList/selectSerieModal.jsx +++ b/src/components/annotationsList/selectSerieModal.jsx @@ -66,12 +66,15 @@ class selectSerieModal extends React.Component { } this.setState({ selectionType }); - // For mammogram studies (all series have examType MG), allow up to 8 significant series + // For mammogram studies, allow up to 8 significant series. + // Exclude PR (presentation state) files before checking — they are supplementary + // and should not prevent a study from being identified as mammogram. const allSeriesFlat = (Array.isArray(this.props.seriesPassed) ? this.props.seriesPassed : Object.values(this.props.seriesPassed) ).flat(); - if (allSeriesFlat.length > 0 && allSeriesFlat.every(s => (s.examType || s.modality)?.toUpperCase() === 'MG')) { + const primarySeries = allSeriesFlat.filter(s => (s.examType || s.modality)?.toUpperCase() !== 'PR'); + if (primarySeries.length > 0 && primarySeries.every(s => (s.examType || s.modality)?.toUpperCase() === 'MG')) { this.maxPort = 8; } diff --git a/src/components/display/SeriesOrderModal.jsx b/src/components/display/SeriesOrderModal.jsx index 31c4eaea6..e4b212072 100644 --- a/src/components/display/SeriesOrderModal.jsx +++ b/src/components/display/SeriesOrderModal.jsx @@ -49,13 +49,16 @@ export default function SeriesOrderModal({ show, onClose, projectID, subjectUID, // Track which initially-ordered series have a displayState ordered.forEach(s => { if (hasDisplayState(s)) initialWithState.current.add(s.seriesUID); }); - // Build page grid + // Build page grid. Series whose significanceOrder overflows a page (e.g. legacy MG + // data with significanceOrder 1–8 and no pageOrder) wrap to the next page automatically. const pageMap = {}; ordered.forEach(s => { - const pageIdx = (s.pageOrder != null ? s.pageOrder : 1) - 1; + const basePage = Math.max((s.pageOrder != null ? s.pageOrder : 1) - 1, 0); const slotIdx = (s.significanceOrder || 1) - 1; + const pageIdx = basePage + Math.floor(slotIdx / SLOTS); + const effectiveSlot = slotIdx % SLOTS; if (!pageMap[pageIdx]) pageMap[pageIdx] = Array(SLOTS).fill(null); - pageMap[pageIdx][Math.min(slotIdx, SLOTS - 1)] = s; + pageMap[pageIdx][effectiveSlot] = s; }); const maxPage = ordered.length > 0 ? Math.max(...Object.keys(pageMap).map(Number)) : 0; diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index df3620dec..03021ca1b 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -3001,7 +3001,7 @@ class DisplayView extends Component { isMammogramOpen = () => { const { series, mammogramSeries } = this.props; const hasMammoSeries = !!(mammogramSeries && mammogramSeries.length > 0); - const hasOpenMG = !!(series && series.some(s => s && s.examType?.toUpperCase() === 'MG')); + const hasOpenMG = !!(series && series.some(s => s && (s.examType || s.modality)?.toUpperCase() === 'MG')); return hasMammoSeries && hasOpenMG; }; @@ -3602,7 +3602,7 @@ class DisplayView extends Component { >
- {this.props.series && this.props.series[i] && this.props.series[i].examType?.toUpperCase() === 'MG' && ( + {this.props.series && this.props.series[i] && (this.props.series[i].examType || this.props.series[i].modality)?.toUpperCase() === 'MG' && ( Date: Tue, 26 May 2026 15:52:08 -0700 Subject: [PATCH 27/34] Add TRIM/UNTRIM button to mammogram toolbar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shrinks viewport containers to match image aspect ratio, eliminating black bars without cropping tissue. Uses CSS Grid to lock the 2×2 layout when trimming in 4-viewport mode, with paired viewports aligned toward each other to avoid gaps. Available in both standard and expanded (2-viewport) layouts. Co-Authored-By: Claude Sonnet 4.6 --- src/components/display/displayView.jsx | 86 ++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/src/components/display/displayView.jsx b/src/components/display/displayView.jsx index 03021ca1b..1bf2ff625 100644 --- a/src/components/display/displayView.jsx +++ b/src/components/display/displayView.jsx @@ -217,6 +217,8 @@ class DisplayView extends Component { mammoExpanded: false, hiddenPorts: new Set(), expandedOrder: null, + trimMode: false, + trimmedDimensions: {}, showReorderModal: false, showSaveStatusWarning: false, pendingSaveStatusData: null, @@ -3177,7 +3179,7 @@ class DisplayView extends Component { /** Restore all viewports to the standard grid layout and clear dot selections. */ restoreMammoExpand = () => { this.setState( - { mammoExpanded: false, selectedPorts: new Set(), hiddenPorts: new Set(), expandedOrder: null }, + { mammoExpanded: false, selectedPorts: new Set(), hiddenPorts: new Set(), expandedOrder: null, trimMode: false, trimmedDimensions: {} }, () => { this.getViewports(); window.dispatchEvent(new CustomEvent("resize", { detail: { isMaximize: false } })); @@ -3187,7 +3189,59 @@ class DisplayView extends Component { /** Clear mammogram selection state (dots + expand). Called on NEXT and new study. */ clearMammoSelection = () => { - this.setState({ selectedPorts: new Set(), mammoExpanded: false, hiddenPorts: new Set(), expandedOrder: null }); + this.setState({ selectedPorts: new Set(), mammoExpanded: false, hiddenPorts: new Set(), expandedOrder: null, trimMode: false, trimmedDimensions: {} }); + }; + + handleTrimMode = () => { + const next = !this.state.trimMode; + const { expandedOrder, hiddenPorts, data } = this.state; + // When expanded: trim only the 2 visible expanded viewports. + // When not expanded: trim all visible viewports. + const targetIndices = expandedOrder + ? expandedOrder + : data.map((_, i) => i).filter(i => !hiddenPorts.has(i)); + + const resizeTargets = () => { + setTimeout(() => { + targetIndices.forEach(i => { + try { + const elements = cornerstone.getEnabledElements(); + const el = elements[i] && elements[i].element; + if (el) cornerstone.resize(el, true); + } catch (e) {} + }); + }, 50); + }; + + if (!next) { + this.setState({ trimMode: false, trimmedDimensions: {} }, resizeTargets); + return; + } + + const trimmedDimensions = {}; + targetIndices.forEach(i => { + try { + const containerEl = this.viewportRefs[i] && this.viewportRefs[i].current; + const elements = cornerstone.getEnabledElements(); + const el = elements[i] && elements[i].element; + if (!containerEl || !el) return; + const enabledEl = cornerstone.getEnabledElement(el); + const image = enabledEl && enabledEl.image; + if (!image) return; + const imageAspect = image.columns / image.rows; + const containerW = containerEl.clientWidth; + const containerH = containerEl.clientHeight; + if (imageAspect < containerW / containerH) { + // Portrait image: black bars on sides → shrink width + trimmedDimensions[i] = { width: Math.round(containerH * imageAspect) + 'px', height: null }; + } else { + // Landscape image: black bars top/bottom → shrink height + trimmedDimensions[i] = { width: null, height: Math.round(containerW / imageAspect) + 'px' }; + } + } catch (e) {} + }); + + this.setState({ trimMode: true, trimmedDimensions }, resizeTargets); }; // --- End mammogram pagination --- @@ -3416,6 +3470,11 @@ class DisplayView extends Component { const redirect = mode === "teaching" ? "search" : "list"; let invertMap = sessionStorage.getItem("invertMap"); invertMap = invertMap ? JSON.parse(invertMap) : {}; + const { trimMode, width: stateWidth } = this.state; + const vpGridCols = trimMode ? (Math.round(100 / parseFloat(stateWidth)) || 2) : undefined; + const vpWrapperStyle = trimMode + ? { display: 'grid', gridTemplateColumns: `repeat(${vpGridCols}, 1fr)`, alignContent: 'start' } + : { display: 'flex', flexWrap: 'wrap', alignContent: 'flex-start' }; return !Object.entries(series).length ? ( @@ -3470,6 +3529,14 @@ class DisplayView extends Component {
{this.state.mammoExpanded ? "RESTORE" : "EXPAND"}
+ {/*