chore: recovery 2 (#5014)
Co-authored-by: Dan Rukas <dan.rukas@gmail.com> Co-authored-by: Tang Cheng <45505657+tctco@users.noreply.github.com>
278
bun.lock
@ -33,7 +33,7 @@
|
||||
},
|
||||
"addOns/externals/devDependencies": {
|
||||
"name": "@externals/devDependencies",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
@ -120,22 +120,22 @@
|
||||
},
|
||||
"addOns/externals/dicom-microscopy-viewer": {
|
||||
"name": "@externals/dicom-microscopy-viewer",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"dicom-microscopy-viewer": "^0.46.1",
|
||||
},
|
||||
},
|
||||
"extensions/cornerstone": {
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/ai": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.10.1",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/ai": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.11.6",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
@ -152,9 +152,9 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.10.1",
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.11.6",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -166,19 +166,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-pmap": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-pmap",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/i18n": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/i18n": "3.10.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -189,16 +189,16 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-rt": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"react-color": "^2.19.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/i18n": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/i18n": "3.10.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -209,19 +209,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-seg": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/i18n": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/i18n": "3.10.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -232,19 +232,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dicom-sr": {
|
||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-measurement-tracking": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -254,19 +254,19 @@
|
||||
},
|
||||
"extensions/cornerstone-dynamic-volume": {
|
||||
"name": "@ohif/extension-cornerstone-dynamic-volume",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/i18n": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/i18n": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -276,7 +276,7 @@
|
||||
},
|
||||
"extensions/default": {
|
||||
"name": "@ohif/extension-default",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/calculate-suv": "^1.1.0",
|
||||
@ -284,8 +284,8 @@
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/i18n": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/i18n": "3.10.1",
|
||||
"dcmjs": "*",
|
||||
"dicomweb-client": "^0.10.4",
|
||||
"prop-types": "^15.6.2",
|
||||
@ -299,7 +299,7 @@
|
||||
},
|
||||
"extensions/dicom-microscopy": {
|
||||
"name": "@ohif/extension-dicom-microscopy",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
@ -310,10 +310,10 @@
|
||||
"mathjs": "^12.4.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/i18n": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/i18n": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@ -324,14 +324,14 @@
|
||||
},
|
||||
"extensions/dicom-pdf": {
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -341,14 +341,14 @@
|
||||
},
|
||||
"extensions/dicom-video": {
|
||||
"name": "@ohif/extension-dicom-video",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -358,20 +358,20 @@
|
||||
},
|
||||
"extensions/measurement-tracking": {
|
||||
"name": "@ohif/extension-measurement-tracking",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"xstate": "^4.10.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"classnames": "^2.3.2",
|
||||
"dcmjs": "*",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
@ -384,14 +384,14 @@
|
||||
},
|
||||
"extensions/test-extension": {
|
||||
"name": "@ohif/extension-test",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"dcmjs": "0.38.0",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@ -401,7 +401,7 @@
|
||||
},
|
||||
"extensions/tmtv": {
|
||||
"name": "@ohif/extension-tmtv",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2",
|
||||
@ -418,7 +418,7 @@
|
||||
},
|
||||
"modes/basic-dev-mode": {
|
||||
"name": "@ohif/mode-basic-dev-mode",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -428,17 +428,17 @@
|
||||
"webpack-merge": "^5.7.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0",
|
||||
"@ohif/extension-dicom-video": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/extension-dicom-pdf": "3.10.1",
|
||||
"@ohif/extension-dicom-video": "3.10.1",
|
||||
},
|
||||
},
|
||||
"modes/basic-test-mode": {
|
||||
"name": "@ohif/mode-test",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -448,19 +448,19 @@
|
||||
"webpack-merge": "^5.7.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0",
|
||||
"@ohif/extension-dicom-video": "3.10.0",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0",
|
||||
"@ohif/extension-test": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/extension-dicom-pdf": "3.10.1",
|
||||
"@ohif/extension-dicom-video": "3.10.1",
|
||||
"@ohif/extension-measurement-tracking": "3.10.1",
|
||||
"@ohif/extension-test": "3.10.1",
|
||||
},
|
||||
},
|
||||
"modes/longitudinal": {
|
||||
"name": "@ohif/mode-longitudinal",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -470,32 +470,32 @@
|
||||
"webpack-merge": "^5.7.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0",
|
||||
"@ohif/extension-dicom-video": "3.10.0",
|
||||
"@ohif/extension-measurement-tracking": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/extension-dicom-pdf": "3.10.1",
|
||||
"@ohif/extension-dicom-video": "3.10.1",
|
||||
"@ohif/extension-measurement-tracking": "3.10.1",
|
||||
},
|
||||
},
|
||||
"modes/microscopy": {
|
||||
"name": "@ohif/mode-microscopy",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.1",
|
||||
},
|
||||
},
|
||||
"modes/preclinical-4d": {
|
||||
"name": "@ohif/mode-preclinical-4d",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
},
|
||||
@ -514,7 +514,7 @@
|
||||
},
|
||||
"modes/segmentation": {
|
||||
"name": "@ohif/mode-segmentation",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -558,7 +558,7 @@
|
||||
},
|
||||
"modes/tmtv": {
|
||||
"name": "@ohif/mode-tmtv",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3",
|
||||
@ -579,32 +579,32 @@
|
||||
},
|
||||
"platform/app": {
|
||||
"name": "@ohif/app",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.10.1",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.11.6",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@ohif/core": "3.10.0",
|
||||
"@ohif/extension-cornerstone": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.0",
|
||||
"@ohif/extension-default": "3.10.0",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.0",
|
||||
"@ohif/extension-dicom-pdf": "3.10.0",
|
||||
"@ohif/extension-dicom-video": "3.10.0",
|
||||
"@ohif/extension-test": "3.10.0",
|
||||
"@ohif/i18n": "3.10.0",
|
||||
"@ohif/mode-basic-dev-mode": "3.10.0",
|
||||
"@ohif/mode-longitudinal": "3.10.0",
|
||||
"@ohif/mode-microscopy": "3.10.0",
|
||||
"@ohif/mode-test": "3.10.0",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@ohif/ui-next": "3.10.0",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-seg": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
"@ohif/extension-dicom-microscopy": "3.10.1",
|
||||
"@ohif/extension-dicom-pdf": "3.10.1",
|
||||
"@ohif/extension-dicom-video": "3.10.1",
|
||||
"@ohif/extension-test": "3.10.1",
|
||||
"@ohif/i18n": "3.10.1",
|
||||
"@ohif/mode-basic-dev-mode": "3.10.1",
|
||||
"@ohif/mode-longitudinal": "3.10.1",
|
||||
"@ohif/mode-microscopy": "3.10.1",
|
||||
"@ohif/mode-test": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"@ohif/ui-next": "3.10.1",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"classnames": "^2.3.2",
|
||||
@ -645,7 +645,7 @@
|
||||
},
|
||||
"platform/cli": {
|
||||
"name": "@ohif/cli",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"bin": {
|
||||
"ohif-cli": "src/index.js",
|
||||
},
|
||||
@ -669,7 +669,7 @@
|
||||
},
|
||||
"platform/core": {
|
||||
"name": "@ohif/core",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"dcmjs": "*",
|
||||
@ -694,16 +694,16 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.10.1",
|
||||
"@ohif/ui": "3.10.0",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.11.6",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21",
|
||||
},
|
||||
},
|
||||
"platform/i18n": {
|
||||
"name": "@ohif/i18n",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next-locize-backend": "^2.0.0",
|
||||
@ -728,7 +728,7 @@
|
||||
},
|
||||
"platform/ui": {
|
||||
"name": "@ohif/ui",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@testing-library/react": "^13.1.0",
|
||||
"browser-detect": "^0.2.28",
|
||||
@ -788,7 +788,7 @@
|
||||
},
|
||||
"platform/ui-next": {
|
||||
"name": "@ohif/ui-next",
|
||||
"version": "3.10.0",
|
||||
"version": "3.10.1",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
@ -1099,9 +1099,9 @@
|
||||
|
||||
"@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
|
||||
|
||||
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.10.1", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "^3.10.1", "@cornerstonejs/tools": "^3.10.1" } }, "sha512-uKic+EyF8SguEM9ahZK96r1oWetjO2GCCEM1oO15wl71iHVXFbW8lUYD1uqXtNW4fZet3X9rEke/te1zNVafxA=="],
|
||||
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@3.11.7", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "ndarray": "^1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "^3.11.7", "@cornerstonejs/tools": "^3.11.7" } }, "sha512-21ewTLHbwHcoALMqXQMuoZTHnAJ4EWuMtLvCHXL43BfVerzDXjLTuPuOeyg2QP4V4krevKH/oyOF04deHWHpsA=="],
|
||||
|
||||
"@cornerstonejs/ai": ["@cornerstonejs/ai@3.10.1", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "lodash.clonedeep": "^4.5.0", "ndarray": "^1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "^3.10.1", "@cornerstonejs/tools": "^3.10.1" } }, "sha512-yEALz91yZCJdrRhk2R5Vn1yFefy07FL9g3ff4BgL19nTnGIu/3yUkVR9PY5js8T6E56GCFWxs9meWdeiCuRAcg=="],
|
||||
"@cornerstonejs/ai": ["@cornerstonejs/ai@3.11.7", "", { "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", "dcmjs": "^0.29.8", "gl-matrix": "^3.4.3", "lodash.clonedeep": "^4.5.0", "ndarray": "^1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "^3.11.7", "@cornerstonejs/tools": "^3.11.7" } }, "sha512-oVJzZNwyjh6UTzvJanYIDjXKcxvT4u9cOLuvzt3pBGbdaeq5Xn/MlxMi90EBvLIuUGgxT/u9MDu4xpBJLVJalw=="],
|
||||
|
||||
"@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="],
|
||||
|
||||
@ -1113,15 +1113,15 @@
|
||||
|
||||
"@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],
|
||||
|
||||
"@cornerstonejs/core": ["@cornerstonejs/core@3.10.1", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-DMlE98A5Oa81G9K5J4ZD1917SbJYteh+j4oJTY60KHAI7p0GoGQRV/ZcxaL+FS8mWng/qejVss9w4fLoLJcErg=="],
|
||||
"@cornerstonejs/core": ["@cornerstonejs/core@3.11.7", "", { "dependencies": { "@kitware/vtk.js": "32.12.1", "comlink": "^4.4.1", "gl-matrix": "^3.4.3", "loglevel": "^1.9.2" } }, "sha512-mjWC603R3hGE8NuYxU89nt3TsRoROci5MC0FIIpLNrdPedizdz8F5PYzUCxdHREIrfy4nDyvPmfK1tdGDqvDtQ=="],
|
||||
|
||||
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.10.1", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "dicom-parser": "^1.8.9", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.10.1" } }, "sha512-w/55TI05KV3mP/RtL0ds+jfh19KlBZajJDgXystV489duvqJJUH27C+eZyrGFqbA3a+EDv4GYQa4jw/MDkGGqA=="],
|
||||
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@3.11.7", "", { "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.5", "comlink": "^4.4.1", "dicom-parser": "^1.8.9", "jpeg-lossless-decoder-js": "^2.1.0", "pako": "^2.0.4", "uuid": "^9.0.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.11.7" } }, "sha512-UzFD/uDnNN3VNuuIq+oWLXRP8Vuxy/n3ZSL4ZxFWuAyQmgM/9CGlJhKmXrmyGXzFnHFnI2hDTuvg54/A2NqMBg=="],
|
||||
|
||||
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@3.10.1", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "^3.10.1", "@cornerstonejs/tools": "^3.10.1", "@kitware/vtk.js": "32.12.1" } }, "sha512-hcAR83rWA6lBYKXgYOWR/3ofhV3mqh4RRLbj8QnS9e//qtmF+UxoZ4I0125ihPkCjFqFOWuYgn42BnRZNhvjxA=="],
|
||||
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@3.11.7", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "^3.11.7", "@cornerstonejs/tools": "^3.11.7", "@kitware/vtk.js": "32.12.1" } }, "sha512-y7+E98bLhRzaIN9v7CtUS+Pqf9yJYI4vukz7gd2p2524/5iPvTqz1v3nfV3jUCZHU+FS27zcN0LNtvlbnPfZRw=="],
|
||||
|
||||
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.10.1", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.10.1", "@cornerstonejs/tools": "^3.10.1", "@kitware/vtk.js": "32.12.1" } }, "sha512-HErrnX7kDs5CVfcuPW5pyWw3lJovqqqKiIfPJdEmZNEM4CVSv9pNpYIbSX8uv42zWq+biQFkP4TwYw12q/r7Og=="],
|
||||
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@3.11.7", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "^3.11.7", "@cornerstonejs/tools": "^3.11.7", "@kitware/vtk.js": "32.12.1" } }, "sha512-ZZysECCUlEp3L33OJZ6ObGq35OyFTa0fo9+D+Tfv32HcRbrfTRTlcrWU7uLGNo3WBolX/zk0pCubRQjT8TbQiQ=="],
|
||||
|
||||
"@cornerstonejs/tools": ["@cornerstonejs/tools@3.10.1", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.10.1", "@kitware/vtk.js": "32.12.1", "@types/d3-array": "^3.0.4", "@types/d3-interpolate": "^3.0.1", "d3-array": "^3.2.3", "d3-interpolate": "^3.0.1", "gl-matrix": "^3.4.3" } }, "sha512-L1z8JiDwgbSwz040BSV5KBMMEe7IZyS4xYVhbn6cpfzCxSfevp53EkUZMhLl4bFcYyRPgXu5/KJO6f9wGFhEvA=="],
|
||||
"@cornerstonejs/tools": ["@cornerstonejs/tools@3.11.7", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", "lodash.get": "^4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "^3.11.7", "@kitware/vtk.js": "32.12.1", "@types/d3-array": "^3.0.4", "@types/d3-interpolate": "^3.0.1", "d3-array": "^3.2.3", "d3-interpolate": "^3.0.1", "gl-matrix": "^3.4.3" } }, "sha512-Lo2/mp2J/PDqA/xZu/krQGryCXaDuqj77F9w/wWCjme3NOqrZfhiq/FYEPRsze8ejhXo5A9ChgVQcEeqNyhPkQ=="],
|
||||
|
||||
"@csstools/postcss-cascade-layers": ["@csstools/postcss-cascade-layers@1.1.1", "", { "dependencies": { "@csstools/selector-specificity": "^2.0.2", "postcss-selector-parser": "^6.0.10" }, "peerDependencies": { "postcss": "^8.2" } }, "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA=="],
|
||||
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.10.1",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.11.6",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"dcmjs": "*",
|
||||
@ -51,12 +51,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.10.1",
|
||||
"@cornerstonejs/ai": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.10.1",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/ai": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.11.6",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@ -64,7 +64,7 @@ export const fourUp = {
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
syncGroups: [VOI_SYNC_GROUP, HYDRATE_SEG_SYNC_GROUP],
|
||||
syncGroups: [HYDRATE_SEG_SYNC_GROUP],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
|
||||
@ -71,7 +71,7 @@ export const mprAnd3DVolumeViewport = {
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
},
|
||||
syncGroups: [VOI_SYNC_GROUP, HYDRATE_SEG_SYNC_GROUP],
|
||||
syncGroups: [HYDRATE_SEG_SYNC_GROUP],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
|
||||
@ -47,7 +47,7 @@ export const only3D = {
|
||||
orientation: 'coronal',
|
||||
customViewportProps: {
|
||||
hideOverlays: true,
|
||||
syncGroups: [VOI_SYNC_GROUP, HYDRATE_SEG_SYNC_GROUP],
|
||||
syncGroups: [HYDRATE_SEG_SYNC_GROUP],
|
||||
},
|
||||
},
|
||||
displaySets: [
|
||||
|
||||
@ -43,6 +43,7 @@ import {
|
||||
setupSegmentationDataModifiedHandler,
|
||||
setupSegmentationModifiedHandler,
|
||||
} from './utils/segmentationHandlers';
|
||||
import { initializeWebWorkerProgressHandler } from './utils/initWebWorkerProgressHandler';
|
||||
|
||||
const { registerColormap } = csUtilities.colormap;
|
||||
|
||||
@ -289,10 +290,16 @@ export default async function init({
|
||||
eventTarget.addEventListenerDebounced(
|
||||
EVENTS.ERROR_EVENT,
|
||||
({ detail }) => {
|
||||
// Create a stable ID for deduplication based on error type and message
|
||||
const errorId = `cornerstone-error-${detail.type}-${detail.message.substring(0, 50)}`;
|
||||
|
||||
uiNotificationService.show({
|
||||
title: detail.type,
|
||||
message: detail.message,
|
||||
type: 'error',
|
||||
id: errorId,
|
||||
allowDuplicates: false, // Prevent duplicate error notifications
|
||||
deduplicationInterval: 30000, // 30 seconds deduplication window
|
||||
});
|
||||
},
|
||||
100
|
||||
@ -309,40 +316,6 @@ export default async function init({
|
||||
return { unsubscriptions };
|
||||
}
|
||||
|
||||
function initializeWebWorkerProgressHandler(uiNotificationService) {
|
||||
const activeToasts = new Map();
|
||||
|
||||
// eventTarget.addEventListener(EVENTS.WEB_WORKER_PROGRESS, ({ detail }) => {
|
||||
// const { progress, type, id } = detail;
|
||||
|
||||
// const cacheKey = `${type}-${id}`;
|
||||
// if (progress === 0 && !activeToasts.has(cacheKey)) {
|
||||
// const progressPromise = new Promise((resolve, reject) => {
|
||||
// activeToasts.set(cacheKey, { resolve, reject });
|
||||
// });
|
||||
|
||||
// uiNotificationService.show({
|
||||
// id: cacheKey,
|
||||
// title: `${type}`,
|
||||
// message: `${type}: ${progress}%`,
|
||||
// autoClose: false,
|
||||
// promise: progressPromise,
|
||||
// promiseMessages: {
|
||||
// loading: `Computing...`,
|
||||
// success: `Completed successfully`,
|
||||
// error: 'Web Worker failed',
|
||||
// },
|
||||
// });
|
||||
// } else {
|
||||
// if (progress === 100) {
|
||||
// const { resolve } = activeToasts.get(cacheKey);
|
||||
// resolve({ progress, type });
|
||||
// activeToasts.delete(cacheKey);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapped image load strategy with metadata handling
|
||||
* @param strategyFn - The image loading strategy function to wrap
|
||||
|
||||
@ -48,6 +48,8 @@ import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
|
||||
|
||||
export default function initCornerstoneTools(configuration = {}) {
|
||||
CrosshairsTool.isAnnotation = false;
|
||||
LabelmapSlicePropagationTool.isAnnotation = false;
|
||||
MarkerLabelmapTool.isAnnotation = false;
|
||||
ReferenceLinesTool.isAnnotation = false;
|
||||
AdvancedMagnifyTool.isAnnotation = false;
|
||||
PlanarFreehandContourSegmentationTool.isAnnotation = false;
|
||||
|
||||
@ -180,15 +180,14 @@ const CornerstoneViewportDownloadForm = ({
|
||||
const toolGroup = ToolGroupManager.getToolGroupForViewport(activeViewportId, renderingEngineId);
|
||||
toolGroup.addViewport(downloadViewportId, renderingEngineId);
|
||||
|
||||
Object.keys(toolGroup.getToolInstances()).forEach(toolName => {
|
||||
if (show && toolName !== 'Crosshairs') {
|
||||
try {
|
||||
toolGroup.setToolEnabled(toolName);
|
||||
} catch (error) {
|
||||
console.debug('Error enabling tool:', error);
|
||||
}
|
||||
const toolInstances = toolGroup.getToolInstances();
|
||||
const toolInstancesArray = Object.values(toolInstances);
|
||||
|
||||
toolInstancesArray.forEach(toolInstance => {
|
||||
if (show && toolInstance.constructor.isAnnotation !== false) {
|
||||
toolGroup.setToolEnabled(toolInstance.toolName);
|
||||
} else {
|
||||
toolGroup.setToolDisabled(toolName);
|
||||
toolGroup.setToolDisabled(toolInstance.toolName);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
110
extensions/cornerstone/src/utils/initWebWorkerProgressHandler.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { eventTarget, EVENTS } from '@cornerstonejs/core';
|
||||
import * as cornerstoneTools from '@cornerstonejs/tools';
|
||||
|
||||
/**
|
||||
* Initializes a handler for web worker progress events.
|
||||
* Tracks active worker tasks and shows notifications for their progress.
|
||||
*
|
||||
* @param uiNotificationService - The UI notification service for showing progress notifications
|
||||
*/
|
||||
export function initializeWebWorkerProgressHandler(uiNotificationService: any) {
|
||||
// Use a single map to track all active worker tasks
|
||||
const activeWorkerTasks = new Map();
|
||||
|
||||
// Create a normalized task key that doesn't include the random ID
|
||||
// This helps us identify and deduplicate the same type of task
|
||||
const getNormalizedTaskKey = (type: string) => {
|
||||
return `worker-task-${type.toLowerCase().replace(/\s+/g, '-')}`;
|
||||
};
|
||||
|
||||
eventTarget.addEventListener(EVENTS.WEB_WORKER_PROGRESS, ({ detail }) => {
|
||||
let normalizedKey: string | undefined;
|
||||
let shouldCleanup = false;
|
||||
|
||||
try {
|
||||
const { progress, type, id } = detail;
|
||||
|
||||
// Skip notifications for compute statistics
|
||||
if (type === cornerstoneTools.Enums.WorkerTypes.COMPUTE_STATISTICS) {
|
||||
return;
|
||||
}
|
||||
|
||||
normalizedKey = getNormalizedTaskKey(type);
|
||||
|
||||
if (progress === 0) {
|
||||
// Check if we're already tracking a task of this type
|
||||
if (!activeWorkerTasks.has(normalizedKey)) {
|
||||
const progressPromise = new Promise((resolve, reject) => {
|
||||
try {
|
||||
activeWorkerTasks.set(normalizedKey, {
|
||||
resolve,
|
||||
reject,
|
||||
originalId: id,
|
||||
type,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error setting active worker task for type "${type}":`, error);
|
||||
reject(error);
|
||||
throw error; // Re-throw to trigger outer catch and cleanup
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
uiNotificationService.show({
|
||||
id: normalizedKey, // Use the normalized key as ID for better deduplication
|
||||
title: `${type}`,
|
||||
message: `Computing...`,
|
||||
autoClose: false,
|
||||
allowDuplicates: false,
|
||||
deduplicationInterval: 60000, // 60 seconds - prevent frequent notifications of same type
|
||||
promise: progressPromise,
|
||||
promiseMessages: {
|
||||
loading: `Computing...`,
|
||||
success: `Completed successfully`,
|
||||
error: 'Web Worker failed',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error showing web worker notification for type "${type}":`, error);
|
||||
shouldCleanup = true;
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
// Already tracking this type of task, just let it continue
|
||||
console.debug(`Already tracking a "${type}" task, skipping duplicate notification`);
|
||||
}
|
||||
}
|
||||
// Task completed
|
||||
else if (progress === 100) {
|
||||
// Check if we have this task type in our tracking map
|
||||
const taskData = activeWorkerTasks.get(normalizedKey);
|
||||
|
||||
if (taskData) {
|
||||
// Resolve the promise to update the notification
|
||||
const { resolve } = taskData;
|
||||
resolve({ progress, type });
|
||||
|
||||
// Mark for cleanup
|
||||
shouldCleanup = true;
|
||||
|
||||
console.debug(`Worker task "${type}" completed successfully`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in web worker progress handler for type "${detail?.type}":`, error);
|
||||
shouldCleanup = true;
|
||||
} finally {
|
||||
// Clean up if needed
|
||||
if (shouldCleanup && normalizedKey) {
|
||||
try {
|
||||
activeWorkerTasks.delete(normalizedKey);
|
||||
} catch (cleanupError) {
|
||||
console.error(
|
||||
`Error cleaning up active worker task for type "${detail?.type}":`,
|
||||
cleanupError
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -80,6 +80,7 @@ export default class ContextMenuController {
|
||||
this.services.uiDialogService.hide('context-menu');
|
||||
this.services.uiDialogService.show({
|
||||
id: 'context-menu',
|
||||
showOverlay: false,
|
||||
defaultPosition: ContextMenuController._getDefaultPosition(
|
||||
defaultPointsPosition,
|
||||
event?.detail || event,
|
||||
|
||||
@ -128,7 +128,9 @@ const DicomTagBrowser = ({
|
||||
<div className="mb-6 flex flex-row items-start pl-1">
|
||||
<div className="flex w-full flex-row items-start gap-4">
|
||||
<div className="flex w-1/3 flex-col">
|
||||
<span className="text-muted-foreground flex h-6 items-center text-xs">Series</span>
|
||||
<span className="text-muted-foreground flex h-6 items-center pb-2 text-base">
|
||||
Series
|
||||
</span>
|
||||
<Select
|
||||
value={selectedDisplaySetInstanceUID}
|
||||
onValueChange={value => onSelectChange({ value })}
|
||||
@ -154,7 +156,7 @@ const DicomTagBrowser = ({
|
||||
</div>
|
||||
{shouldShowInstanceList && (
|
||||
<div className="mx-auto mt-0.5 flex w-1/4 flex-col">
|
||||
<span className="text-muted-foreground flex h-6 items-center text-xs">
|
||||
<span className="text-muted-foreground flex h-6 items-center pb-2 text-base">
|
||||
Instance Number ({instanceNumber} of {activeDisplaySet?.images?.length})
|
||||
</span>
|
||||
<Slider
|
||||
@ -170,7 +172,7 @@ const DicomTagBrowser = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-auto mr-1 flex w-1/3 flex-col">
|
||||
<span className="text-muted-foreground flex h-6 items-center text-xs">
|
||||
<span className="text-muted-foreground flex h-6 items-center pb-2 text-base">
|
||||
Search metadata
|
||||
</span>
|
||||
<InputFilter
|
||||
@ -182,7 +184,7 @@ const DicomTagBrowser = ({
|
||||
placeholder="Search metadata"
|
||||
className="pl-9 pr-9"
|
||||
/>
|
||||
<InputFilter.ClearButton />
|
||||
<InputFilter.ClearButton className="text-primary mr-0.5 p-0.5" />
|
||||
</InputFilter>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@ const collapsedOutsideBorderSize = 4;
|
||||
const collapsedWidth = 25;
|
||||
|
||||
const rightPanelInitialExpandedWidth = 280;
|
||||
const leftPanelInitialExpandedWidth = 292;
|
||||
const leftPanelInitialExpandedWidth = 282;
|
||||
|
||||
const panelGroupDefinition = {
|
||||
groupId: 'viewerLayoutResizablePanelGroup',
|
||||
|
||||
@ -37,6 +37,8 @@ import * as utils from './utils';
|
||||
import { Toolbox } from './utils';
|
||||
import MoreDropdownMenu from './Components/MoreDropdownMenu';
|
||||
import requestDisplaySetCreationForStudy from './Panels/requestDisplaySetCreationForStudy';
|
||||
import { Toolbar } from './Toolbar/Toolbar';
|
||||
|
||||
const defaultExtension: Types.Extensions.Extension = {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
@ -103,4 +105,5 @@ export {
|
||||
requestDisplaySetCreationForStudy,
|
||||
callInputDialog,
|
||||
createReportDialogPrompt,
|
||||
Toolbar,
|
||||
};
|
||||
|
||||
@ -32,8 +32,8 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/tools": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.10.1",
|
||||
"@ohif/extension-default": "3.10.1",
|
||||
|
||||
@ -120,8 +120,8 @@ const toolbarButtons: Button[] = [
|
||||
tooltip: 'Show Reference Lines',
|
||||
commands: 'toggleEnabledDisabledToolbar',
|
||||
listeners: {
|
||||
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: callbacks('ReferenceLinesTool'),
|
||||
[ViewportGridService.EVENTS.VIEWPORTS_READY]: callbacks('ReferenceLinesTool'),
|
||||
[ViewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: callbacks('ReferenceLines'),
|
||||
[ViewportGridService.EVENTS.VIEWPORTS_READY]: callbacks('ReferenceLines'),
|
||||
},
|
||||
evaluate: [
|
||||
'evaluate.cornerstoneTool.toggle',
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
"test:e2e": "lerna run test:e2e --stream",
|
||||
"test:e2e:ci": "TEST_ENV=true npx playwright test",
|
||||
"test:e2e:ui": "TEST_ENV=true npx playwright test --ui",
|
||||
"test:e2e:update": "TEST_ENV=true npx playwright test --update-snapshots",
|
||||
"test:e2e:headed": "TEST_ENV=true npx playwright test --headed",
|
||||
"test:e2e:debug": "TEST_ENV=true npx playwright test --debug",
|
||||
"test:e2e:dist": "lerna run test:e2e:dist --stream",
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.10.1",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.11.6",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@ohif/core": "3.10.1",
|
||||
"@ohif/extension-cornerstone": "3.10.1",
|
||||
|
||||
@ -37,8 +37,8 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/core": "^3.10.1",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.10.1",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.11.6",
|
||||
"@ohif/ui": "3.10.1",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21"
|
||||
|
||||
@ -215,7 +215,10 @@ class MetadataProvider {
|
||||
break;
|
||||
case WADO_IMAGE_LOADER_TAGS.MODALITY_LUT_MODULE:
|
||||
const { RescaleIntercept, RescaleSlope } = instance;
|
||||
if (RescaleIntercept === undefined || RescaleSlope === undefined) {
|
||||
// Early return if RescaleIntercept or RescaleSlope are not
|
||||
// present (undefined) or explicitly set to null. We use loose
|
||||
// equality in this case to check for *null* or *undefined*.
|
||||
if (RescaleIntercept == null || RescaleSlope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,8 @@ const serviceImplementation = {
|
||||
console.warn('isEmpty() NOT IMPLEMENTED');
|
||||
return true;
|
||||
},
|
||||
_updatePosition: (id: string, position: { x: number; y: number }) =>
|
||||
console.warn('updatePosition() NOT IMPLEMENTED'),
|
||||
_customComponent: null,
|
||||
};
|
||||
|
||||
@ -63,6 +65,16 @@ class UIDialogService {
|
||||
return serviceImplementation._isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position of a specific dialog by id
|
||||
*
|
||||
* @param {string} id - The dialog id to update
|
||||
* @param {{ x: number; y: number }} position - The new position
|
||||
*/
|
||||
updatePosition(id: string, position: { x: number; y: number }): void {
|
||||
return serviceImplementation._updatePosition(id, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* This provides flexibility in customizing the Modal's default component
|
||||
*
|
||||
@ -75,7 +87,14 @@ class UIDialogService {
|
||||
/**
|
||||
* Set the service implementation
|
||||
*/
|
||||
setServiceImplementation({ show, hide, hideAll, isEmpty, customComponent }: any): void {
|
||||
setServiceImplementation({
|
||||
show,
|
||||
hide,
|
||||
hideAll,
|
||||
isEmpty,
|
||||
updatePosition,
|
||||
customComponent,
|
||||
}: any): void {
|
||||
if (show) {
|
||||
serviceImplementation._show = show;
|
||||
}
|
||||
@ -88,6 +107,9 @@ class UIDialogService {
|
||||
if (isEmpty) {
|
||||
serviceImplementation._isEmpty = isEmpty;
|
||||
}
|
||||
if (updatePosition) {
|
||||
serviceImplementation._updatePosition = updatePosition;
|
||||
}
|
||||
if (customComponent) {
|
||||
serviceImplementation._customComponent = customComponent;
|
||||
}
|
||||
|
||||
@ -60,17 +60,24 @@ class UINotificationService {
|
||||
* @param {string} [notification.promiseMessages.loading] - Message to show while promise is pending
|
||||
* @param {string | function} [notification.promiseMessages.success] - Message to show when promise resolves
|
||||
* @param {string | function} [notification.promiseMessages.error] - Message to show when promise rejects
|
||||
* @param {object} [notification.action] - Action button configuration
|
||||
* @param {string} notification.action.label - The label for the action button
|
||||
* @param {function} notification.action.onClick - The function to call when the action button is clicked
|
||||
* @returns {string} id - The ID of the created notification
|
||||
*/
|
||||
show({
|
||||
title,
|
||||
message,
|
||||
duration = 5000,
|
||||
duration = 2000,
|
||||
position = 'bottom-right',
|
||||
type = 'info',
|
||||
autoClose = true,
|
||||
promise,
|
||||
promiseMessages,
|
||||
id,
|
||||
allowDuplicates = false,
|
||||
deduplicationInterval = 30000,
|
||||
action,
|
||||
}: {
|
||||
title: string;
|
||||
message: string | ((data?: any) => string);
|
||||
@ -90,6 +97,13 @@ class UINotificationService {
|
||||
success?: string | ((data: any) => string);
|
||||
error?: string | ((error: any) => string);
|
||||
};
|
||||
id?: string;
|
||||
allowDuplicates?: boolean;
|
||||
deduplicationInterval?: number;
|
||||
action?: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
}): string {
|
||||
if (promise && promiseMessages) {
|
||||
const loadingId = serviceImplementation._show({
|
||||
@ -98,6 +112,9 @@ class UINotificationService {
|
||||
type: 'loading',
|
||||
autoClose: false,
|
||||
position,
|
||||
id: id ? `${id}-loading` : undefined,
|
||||
allowDuplicates,
|
||||
deduplicationInterval,
|
||||
});
|
||||
|
||||
promise.then(
|
||||
@ -114,6 +131,10 @@ class UINotificationService {
|
||||
duration,
|
||||
position,
|
||||
autoClose,
|
||||
id: id ? `${id}-success` : undefined,
|
||||
allowDuplicates,
|
||||
deduplicationInterval,
|
||||
action,
|
||||
});
|
||||
this.hide(loadingId);
|
||||
},
|
||||
@ -130,6 +151,10 @@ class UINotificationService {
|
||||
duration,
|
||||
position,
|
||||
autoClose,
|
||||
id: id ? `${id}-error` : undefined,
|
||||
allowDuplicates,
|
||||
deduplicationInterval,
|
||||
action,
|
||||
});
|
||||
this.hide(loadingId);
|
||||
}
|
||||
@ -145,6 +170,10 @@ class UINotificationService {
|
||||
position,
|
||||
type,
|
||||
autoClose,
|
||||
id,
|
||||
allowDuplicates,
|
||||
deduplicationInterval,
|
||||
action,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,8 +132,9 @@ export default function ComponentShowcase() {
|
||||
description="Colors and Typography"
|
||||
>
|
||||
<div className="text-foreground min-h-screen bg-black">
|
||||
<div className="mx-auto my-4 max-w-5xl pt-6 pb-3">
|
||||
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
|
||||
<div className="mx-auto my-4 max-w-5xl pt-4 pb-6">
|
||||
{/* Navigation cards */}
|
||||
<div className="mb-8 grid grid-cols-1 gap-5 md:grid-cols-3">
|
||||
<a
|
||||
href="/colors-and-type"
|
||||
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
|
||||
@ -183,9 +184,7 @@ export default function ComponentShowcase() {
|
||||
</Card>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto my-4 max-w-5xl pt-4 pb-6">
|
||||
<h1 className="text-foreground ml-6 mb-6 text-5xl">Colors & Typography</h1>
|
||||
|
||||
<ShowcaseRow
|
||||
@ -249,7 +248,7 @@ export default function ComponentShowcase() {
|
||||
<span className="text-foreground text-lg">foreground</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="bg-muted-foreground border-input h-[30px] w-[30px] rounded border"></div>
|
||||
<div className="bg-muted-foreground h-[30px] w-[30px] rounded"></div>
|
||||
<span className="text-foreground text-lg">muted-foreground</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -258,6 +257,23 @@ export default function ComponentShowcase() {
|
||||
use 'muted-foreground' to create separation and readability.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-8 grid grid-cols-[28%,1fr] items-start gap-x-8">
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="bg-input h-[30px] w-[30px] rounded"></div>
|
||||
<span className="text-foreground text-lg">input</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="bg-neutral h-[30px] w-[30px] rounded"></div>
|
||||
<span className="text-foreground text-lg">neutral</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-secondary-foreground flex items-center pt-1.5 text-lg">
|
||||
Used for borders and UI elements. 'neutral' is typically used at 50% opacity for
|
||||
elements such as scrollbars and will work over light and dark backgrounds
|
||||
</div>
|
||||
</div>
|
||||
</ShowcaseRow>
|
||||
|
||||
<ShowcaseRow
|
||||
|
||||
@ -3,12 +3,25 @@ import '../css/custom.css';
|
||||
import Layout from '@theme/Layout';
|
||||
import { TooltipProvider } from '../../../ui-next/src/components/Tooltip';
|
||||
|
||||
// Import all showcase components
|
||||
// Navigation card components
|
||||
import { Card, CardHeader, CardTitle, CardDescription } from '../../../ui-next/src/components/Card';
|
||||
import { Icons } from '../../../ui-next/src/components/Icons';
|
||||
|
||||
/* Showcase components (alphabetical) */
|
||||
import AllinOneMenuShowcase from './components/AllinOneMenuShowcase';
|
||||
import ButtonShowcase from './components/ButtonShowcase';
|
||||
import CheckboxShowcase from './components/CheckboxShowcase';
|
||||
import CinePlayerShowcase from './components/CinePlayerShowcase';
|
||||
import ComboboxShowcase from './components/ComboboxShowcase';
|
||||
import DataRowShowcase from './components/DataRowShowcase';
|
||||
import DialogShowcase from './components/DialogShowcase';
|
||||
import DropdownMenuShowcase from './components/DropdownMenuShowcase';
|
||||
import HoverCardShowcase from './components/HoverCardShowcase';
|
||||
import InputShowcase from './components/InputShowcase';
|
||||
import LabelShowcase from './components/LabelShowcase';
|
||||
import NumericMetaShowcase from './components/NumericMetaShowcase';
|
||||
import PanelSectionShowcase from './components/PanelSectionShowcase';
|
||||
import PopoverShowcase from './components/PopoverShowcase';
|
||||
import ScrollAreaShowcase from './components/ScrollAreaShowcase';
|
||||
import SelectShowcase from './components/SelectShowcase';
|
||||
import SliderShowcase from './components/SliderShowcase';
|
||||
@ -17,7 +30,8 @@ import TabsShowcase from './components/TabsShowcase';
|
||||
import ToastShowcase from './components/ToastShowcase';
|
||||
import ToolButtonShowcase from './components/ToolButtonShowcase';
|
||||
import ToolButtonListShowcase from './components/ToolButtonListShowcase';
|
||||
import NumericMetaShowcase from './components/NumericMetaShowcase';
|
||||
import TooltipShowcase from './components/TooltipShowcase';
|
||||
|
||||
/**
|
||||
* Components List page that displays all available UI components
|
||||
*/
|
||||
@ -30,15 +44,74 @@ export default function ComponentsList() {
|
||||
<TooltipProvider>
|
||||
<div className="text-foreground min-h-screen bg-black">
|
||||
<div className="mx-auto my-4 max-w-5xl pt-4 pb-6">
|
||||
<div className="ml-6 mb-6 text-base">
|
||||
<h1 className="text-foreground mb-3 text-5xl">Components</h1>
|
||||
{/* Navigation cards */}
|
||||
<div className="mb-8 grid grid-cols-1 gap-5 md:grid-cols-3">
|
||||
<a
|
||||
href="/colors-and-type"
|
||||
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
|
||||
>
|
||||
<Card className="hover:bg-primary/30 w-full transition-colors">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-foreground text-xl">
|
||||
<Icons.ColorChange className="h-12 w-12" />
|
||||
Colors & Typography
|
||||
</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
Color Palette and Typography Guidelines
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</a>
|
||||
<a
|
||||
href="/components-list"
|
||||
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
|
||||
>
|
||||
<Card className="hover:bg-primary/30 w-full transition-colors">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-foreground text-xl">
|
||||
<Icons.ColorChange className="h-12 w-12" />
|
||||
Components
|
||||
</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
Essential UI Components with Variants
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</a>
|
||||
<a
|
||||
href="/patterns"
|
||||
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
|
||||
>
|
||||
<Card className="hover:bg-primary/30 w-full transition-colors">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-foreground text-xl">
|
||||
<Icons.ColorChange className="h-12 w-12" />
|
||||
Patterns
|
||||
</CardTitle>
|
||||
<CardDescription className="text-lg">
|
||||
Component-Based Layout Examples
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</a>
|
||||
</div>
|
||||
<TabsShowcase />
|
||||
|
||||
<h1 className="text-foreground ml-6 mb-6 text-5xl">Components</h1>
|
||||
|
||||
<AllinOneMenuShowcase />
|
||||
<ButtonShowcase />
|
||||
<CheckboxShowcase />
|
||||
<CinePlayerShowcase />
|
||||
<ComboboxShowcase />
|
||||
<DataRowShowcase />
|
||||
<DialogShowcase />
|
||||
<DropdownMenuShowcase />
|
||||
<HoverCardShowcase />
|
||||
<InputShowcase />
|
||||
<LabelShowcase />
|
||||
<NumericMetaShowcase />
|
||||
<PanelSectionShowcase />
|
||||
<PopoverShowcase />
|
||||
<ScrollAreaShowcase />
|
||||
<SelectShowcase />
|
||||
<SliderShowcase />
|
||||
@ -47,7 +120,7 @@ export default function ComponentsList() {
|
||||
<ToastShowcase />
|
||||
<ToolButtonShowcase />
|
||||
<ToolButtonListShowcase />
|
||||
<NumericMetaShowcase />
|
||||
<TooltipShowcase />
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
142
platform/docs/src/pages/components/AllinOneMenuShowcase.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import React from 'react';
|
||||
import AllInOneMenu, {
|
||||
IconMenu,
|
||||
SubMenu,
|
||||
ItemPanel,
|
||||
Item,
|
||||
DividerItem,
|
||||
HorizontalDirection,
|
||||
VerticalDirection,
|
||||
} from '../../../../ui-next/src/components/AllInOneMenu';
|
||||
import { Switch } from '../../../../ui-next/src/components/Switch';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* Pure‑UI mock of the in‑app Window/Level menu.
|
||||
* Clickable, but all actions are inert.
|
||||
*/
|
||||
export default function AllinOneMenuShowcase() {
|
||||
/* Helpers to build common static items so the JSX stays concise */
|
||||
const renderColorLUTItems = () =>
|
||||
[
|
||||
'Grayscale',
|
||||
'X Ray',
|
||||
'HSV',
|
||||
'Hot Iron',
|
||||
'Red Hot',
|
||||
'S PET',
|
||||
'Perfusion',
|
||||
'Rainbow',
|
||||
'SUV',
|
||||
'GE 256',
|
||||
'GE',
|
||||
'Siemens',
|
||||
].map(name => <Item key={name} label={name} />);
|
||||
|
||||
const renderWindowPresetItems = () =>
|
||||
[
|
||||
{ desc: 'Soft tissue', wl: '400 / 40' },
|
||||
{ desc: 'Lung', wl: '1500 / -600' },
|
||||
{ desc: 'Liver', wl: '150 / 90' },
|
||||
{ desc: 'Bone', wl: '2500 / 480' },
|
||||
{ desc: 'Brain', wl: '80 / 40' },
|
||||
].map(p => (
|
||||
<Item
|
||||
key={p.desc}
|
||||
label={p.desc}
|
||||
secondaryLabel={p.wl}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="All In One Menu"
|
||||
description="A structured, consolidated menu designed to reduce visual clutter, helping users keep their focus on the image. It supports various UI components—including Switches, Numeric Inputs, Tabs, and Sliders—for detailed settings, as well as text-based lists for actionable items."
|
||||
code={`<IconMenu icon="viewport-window-level" menuStyle={{ width: 212, maxHeight: 500 }}>
|
||||
<ItemPanel label="Display">
|
||||
<Item label="Display Color bar" rightIcon={<Switch disabled />} />
|
||||
<DividerItem />
|
||||
<SubMenu itemLabel="Color LUT" itemIcon="icon-color-lut">
|
||||
<ItemPanel label="Color LUTs">
|
||||
<Item label="Preview in viewport" rightIcon={<Switch disabled />} />
|
||||
<DividerItem />
|
||||
<Item label="Grayscale" />
|
||||
<Item label="X Ray" />
|
||||
<Item label="HSV" />
|
||||
<Item label="Hot Iron" />
|
||||
<Item label="Red Hot" />
|
||||
<Item label="S PET" />
|
||||
<Item label="Perfusion" />
|
||||
<Item label="Rainbow" />
|
||||
<Item label="SUV" />
|
||||
<Item label="GE 256" />
|
||||
<Item label="GE" />
|
||||
<Item label="Siemens" />
|
||||
</ItemPanel>
|
||||
</SubMenu>
|
||||
<SubMenu itemLabel="Window Presets" itemIcon="viewport-window-level">
|
||||
<ItemPanel label="CT Presets">
|
||||
<Item label="Soft tissue" secondaryLabel="400 / 40" />
|
||||
<Item label="Lung" secondaryLabel="1500 / ‑600" />
|
||||
<Item label="Liver" secondaryLabel="150 / 90" />
|
||||
<Item label="Bone" secondaryLabel="2500 / 480" />
|
||||
<Item label="Brain" secondaryLabel="80 / 40" />
|
||||
</ItemPanel>
|
||||
</SubMenu>
|
||||
</ItemPanel>
|
||||
</IconMenu>`}
|
||||
>
|
||||
<div className="border-input/70 relative flex h-12 items-center rounded border bg-black px-4">
|
||||
<IconMenu
|
||||
icon="viewport-window-level"
|
||||
iconClassName="text-xl text-highlight hover:bg-primary/30 cursor-pointer rounded"
|
||||
horizontalDirection={HorizontalDirection.LeftToRight}
|
||||
verticalDirection={VerticalDirection.TopToBottom}
|
||||
menuStyle={{ width: 212, maxHeight: 500 }}
|
||||
>
|
||||
<ItemPanel label="Display">
|
||||
<Item
|
||||
label="Display Color bar"
|
||||
rightIcon={
|
||||
<Switch
|
||||
checked={false}
|
||||
disabled
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
}
|
||||
useIconSpace={false}
|
||||
/>
|
||||
<DividerItem />
|
||||
<SubMenu itemLabel="Color LUT" itemIcon="icon-color-lut">
|
||||
<ItemPanel
|
||||
label="Color LUTs"
|
||||
maxHeight="calc(100vh - 250px)"
|
||||
className="flex flex-col"
|
||||
>
|
||||
<Item
|
||||
label="Preview in viewport"
|
||||
rightIcon={
|
||||
<Switch
|
||||
checked={false}
|
||||
disabled
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<DividerItem />
|
||||
{renderColorLUTItems()}
|
||||
</ItemPanel>
|
||||
</SubMenu>
|
||||
<SubMenu itemLabel="Window Presets" itemIcon="viewport-window-level">
|
||||
<ItemPanel label="CT Presets">{renderWindowPresetItems()}</ItemPanel>
|
||||
</SubMenu>
|
||||
</ItemPanel>
|
||||
</IconMenu>
|
||||
|
||||
<span className="text-muted-foreground ml-3 text-sm">
|
||||
Click the icon to explore an example
|
||||
</span>
|
||||
</div>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
37
platform/docs/src/pages/components/CinePlayerShowcase.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { useState } from 'react';
|
||||
import CinePlayer from '../../../../ui-next/src/components/CinePlayer/CinePlayer';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* CinePlayerShowcase displays a playable/pausable cine player with FPS control.
|
||||
*/
|
||||
export default function CinePlayerShowcase() {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [fps, setFps] = useState(24);
|
||||
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Cine Player"
|
||||
description="Play, pause, scrub through dynamic image series and adjust FPS."
|
||||
code={`
|
||||
<CinePlayer
|
||||
className="w-[300px]"
|
||||
isPlaying={isPlaying}
|
||||
frameRate={fps}
|
||||
onPlayPauseChange={setIsPlaying}
|
||||
onFrameRateChange={setFps}
|
||||
onClose={() => console.log('close clicked')}
|
||||
/>
|
||||
`}
|
||||
>
|
||||
<CinePlayer
|
||||
className="w-[300px]"
|
||||
isPlaying={isPlaying}
|
||||
frameRate={fps}
|
||||
onPlayPauseChange={setIsPlaying}
|
||||
onFrameRateChange={setFps}
|
||||
onClose={() => console.log('close clicked')}
|
||||
/>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
105
platform/docs/src/pages/components/ComboboxShowcase.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { Combobox } from '../../../../ui-next/src/components/Combobox/Combobox';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* ComboboxShowcase demonstrates the searchable Combobox component with a
|
||||
* comprehensive “Modality” list.
|
||||
*/
|
||||
export default function ComboboxShowcase() {
|
||||
const modalities = [
|
||||
'AR',
|
||||
'ASMT',
|
||||
'AU',
|
||||
'BDUS',
|
||||
'BI',
|
||||
'BMD',
|
||||
'CR',
|
||||
'CT',
|
||||
'CTPROTOCOL',
|
||||
'DG',
|
||||
'DOC',
|
||||
'DX',
|
||||
'ECG',
|
||||
'EPS',
|
||||
'ES',
|
||||
'FID',
|
||||
'GM',
|
||||
'HC',
|
||||
'HD',
|
||||
'IO',
|
||||
'IOL',
|
||||
'IVOCT',
|
||||
'IVUS',
|
||||
'KER',
|
||||
'KO',
|
||||
'LEN',
|
||||
'LS',
|
||||
'MG',
|
||||
'MR',
|
||||
'M3D',
|
||||
'NM',
|
||||
'OAM',
|
||||
'OCT',
|
||||
'OP',
|
||||
'OPM',
|
||||
'OPT',
|
||||
'OPTBSV',
|
||||
'OPTENF',
|
||||
'OPV',
|
||||
'OSS',
|
||||
'OT',
|
||||
'PLAN',
|
||||
'PR',
|
||||
'PT',
|
||||
'PX',
|
||||
'REG',
|
||||
'RESP',
|
||||
'RF',
|
||||
'RG',
|
||||
'RTDOSE',
|
||||
'RTIMAGE',
|
||||
'RTINTENT',
|
||||
'RTPLAN',
|
||||
'RTRAD',
|
||||
'RTRECORD',
|
||||
'RTSEGANN',
|
||||
'RTSTRUCT',
|
||||
'RWV',
|
||||
'SEG',
|
||||
'SM',
|
||||
'SMR',
|
||||
'SR',
|
||||
'SRF',
|
||||
'STAIN',
|
||||
'TEXTUREMAP',
|
||||
'TG',
|
||||
'US',
|
||||
'VA',
|
||||
'XA',
|
||||
'XC',
|
||||
].map(m => ({ value: m, label: m }));
|
||||
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Combobox"
|
||||
description="Searchable dropdown built with Command + Popover primitives."
|
||||
code={`
|
||||
const modalities = [
|
||||
'AR','ASMT','AU','BDUS','BI','BMD','CR','CT','CTPROTOCOL','DG','DOC','DX','ECG','EPS','ES','FID',
|
||||
'GM','HC','HD','IO','IOL','IVOCT','IVUS','KER','KO','LEN','LS','MG','MR','M3D','NM','OAM','OCT',
|
||||
'OP','OPM','OPT','OPTBSV','OPTENF','OPV','OSS','OT','PLAN','PR','PT','PX','REG','RESP','RF','RG',
|
||||
'RTDOSE','RTIMAGE','RTINTENT','RTPLAN','RTRAD','RTRECORD','RTSEGANN','RTSTRUCT','RWV','SEG','SM',
|
||||
'SMR','SR','SRF','STAIN','TEXTUREMAP','TG','US','VA','XA','XC',
|
||||
].map(m => ({ value: m, label: m }));
|
||||
|
||||
<Combobox data={modalities} placeholder="Modality" />
|
||||
`}
|
||||
>
|
||||
<Combobox
|
||||
data={modalities}
|
||||
placeholder="Modality"
|
||||
/>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
82
platform/docs/src/pages/components/DialogShowcase.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
DialogClose,
|
||||
} from '../../../../ui-next/src/components/Dialog/Dialog';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* DialogShowcase demonstrates a simple Radix‑based dialog.
|
||||
*/
|
||||
export default function DialogShowcase() {
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Dialog"
|
||||
description="Modal dialog with header, content, and footer actions."
|
||||
code={`
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="default">Open Dialog</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Example Dialog</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
This is a short message inside the dialog.
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary" className="min-w-[60px]">Cancel</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button variant="default" className="min-w-[60px]">OK</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
`}
|
||||
>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="default">Open Dialog</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Example Dialog</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogDescription>This is a short message inside the dialog.</DialogDescription>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="min-w-[60px]"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
<DialogClose asChild>
|
||||
<Button
|
||||
variant="default"
|
||||
className="min-w-[60px]"
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
39
platform/docs/src/pages/components/HoverCardShowcase.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardTrigger,
|
||||
HoverCardContent,
|
||||
} from '../../../../ui-next/src/components/HoverCard/HoverCard';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* HoverCardShowcase demonstrates a Hover Card that appears on pointer hover.
|
||||
*/
|
||||
export default function HoverCardShowcase() {
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Hover Card"
|
||||
description="Lightweight, non‑modal surface that appears on hover or focus."
|
||||
code={`
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Button variant="link">Hover me</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<p className="text-sm">Hello there! I'm a hover card.</p>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
`}
|
||||
>
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Button variant="link">Hover me</Button>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent>
|
||||
<p className="text-sm">Hello there! I'm a hover card.</p>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
27
platform/docs/src/pages/components/LabelShowcase.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Label } from '../../../../ui-next/src/components/Label/Label';
|
||||
import { Switch } from '../../../../ui-next/src/components/Switch';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* LabelShowcase pairs a Label with a Switch inline.
|
||||
*/
|
||||
export default function LabelShowcase() {
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Label"
|
||||
description="Labels clarify the purpose of controls and improve accessibility."
|
||||
code={`
|
||||
<Switch defaultChecked id="preview-switch" />
|
||||
<Label htmlFor="preview-switch" className="ml-2">
|
||||
Preview edits before creating
|
||||
</Label>
|
||||
`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch defaultChecked id="preview-switch" />
|
||||
<Label htmlFor="preview-switch">Preview edits before creating</Label>
|
||||
</div>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
@ -45,7 +45,7 @@ export default function NumericShowcase() {
|
||||
<Numeric.NumberInput className="w-32 text-center" />
|
||||
</Numeric.Container>`}
|
||||
>
|
||||
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<div className="bg-muted flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<Numeric.Container
|
||||
mode="number"
|
||||
min={0}
|
||||
@ -136,7 +136,7 @@ const [dimensionGroupNumber, setDimensionGroupNumber] = useState(1);
|
||||
</div>
|
||||
</Numeric.Container>`}
|
||||
>
|
||||
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<div className="bg-muted flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<Numeric.Container
|
||||
mode="stepper"
|
||||
value={dimensionGroupNumber}
|
||||
@ -229,7 +229,7 @@ const [controlledValue, setControlledValue] = useState(0);
|
||||
<Numeric.SingleRange showNumberInput />
|
||||
</Numeric.Container>`}
|
||||
>
|
||||
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<div className="bg-muted flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<Numeric.Container
|
||||
mode="singleRange"
|
||||
min={0}
|
||||
@ -314,7 +314,7 @@ const [controlledValues, setControlledValues] = useState<[number, number]>([0, 1
|
||||
<Numeric.DoubleRange />
|
||||
</Numeric.Container>`}
|
||||
>
|
||||
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<div className="bg-muted flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<Numeric.Container
|
||||
mode="doubleRange"
|
||||
min={0}
|
||||
@ -407,7 +407,7 @@ const [controlledValues, setControlledValues] = useState<[number, number]>([0, 1
|
||||
<Numeric.DoubleRange showNumberInputs />
|
||||
</Numeric.Container>`}
|
||||
>
|
||||
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<div className="bg-muted flex w-[300px] flex-col space-y-4 rounded p-4">
|
||||
<Numeric.Container
|
||||
mode="number"
|
||||
min={0}
|
||||
|
||||
52
platform/docs/src/pages/components/PanelSectionShowcase.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
// docs/src/pages/components/PanelSectionShowcase.tsx
|
||||
import React from 'react';
|
||||
import { PanelSection } from '../../../../ui-next/src/components/PanelSection/PanelSection';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* PanelSectionShowcase displays an expandable/collapsible panel section.
|
||||
*/
|
||||
export default function PanelSectionShowcase() {
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Panel Section"
|
||||
description="Collapsible panels for grouping controls or metadata inside side‑panels."
|
||||
code={`
|
||||
<PanelSection defaultOpen>
|
||||
<PanelSection.Header>Series Information</PanelSection.Header>
|
||||
<PanelSection.Content>
|
||||
<div className="p-2 space-y-1 text-sm text-muted-foreground">
|
||||
<div>Images: 120</div>
|
||||
<div>Modality: MR</div>
|
||||
<div>Body Part: Brain</div>
|
||||
<Button variant="link" size="xs">Load another series</Button>
|
||||
</div>
|
||||
</PanelSection.Content>
|
||||
</PanelSection>
|
||||
`}
|
||||
>
|
||||
<PanelSection
|
||||
defaultOpen
|
||||
className="bg-muted w-[280px]"
|
||||
>
|
||||
<PanelSection.Header className="bg-popover">Series Information</PanelSection.Header>
|
||||
<PanelSection.Content>
|
||||
<div className="text-muted-foreground space-y-1 p-2 text-sm">
|
||||
<div className="pl-2">
|
||||
<div>Images: 120</div>
|
||||
<div>Modality: MR</div>
|
||||
<div>Body Part: Brain</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
>
|
||||
Load more information
|
||||
</Button>
|
||||
</div>
|
||||
</PanelSection.Content>
|
||||
</PanelSection>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
43
platform/docs/src/pages/components/PopoverShowcase.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from '../../../../ui-next/src/components/Popover/Popover';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* PopoverShowcase presents a simple Popover that appears after clicking a button.
|
||||
*/
|
||||
export default function PopoverShowcase() {
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Popover"
|
||||
description="Transient panel that appears over content—great for small forms, extra details, or actions."
|
||||
code={`
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="default">Open Popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-60">
|
||||
<p className="text-sm">
|
||||
Hello! I’m a Popover. Click outside or press Esc to close me.
|
||||
</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
`}
|
||||
>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="default">Open Popover</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-60">
|
||||
<p className="text-sm">
|
||||
Hello! I’m a Popover. Click outside or press Esc to close me.
|
||||
</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
@ -12,7 +12,7 @@ export default function TabsShowcase() {
|
||||
title="Tabs"
|
||||
description="Tabs (or segmented controls) can be used to provide navigation options or allow users to switch between multiple options (e.g., tool settings) "
|
||||
code={`
|
||||
<Tabs className="w-[400px]" onValueChange={newValue => console.log(newValue)}>
|
||||
<Tabs defaultValue="circle" className="w-[400px]" onValueChange={newValue => console.log(newValue)}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="circle">Circle</TabsTrigger>
|
||||
<Separator orientation="vertical" />
|
||||
@ -24,6 +24,7 @@ export default function TabsShowcase() {
|
||||
`}
|
||||
>
|
||||
<Tabs
|
||||
defaultValue="circle"
|
||||
className="w-[400px]"
|
||||
onValueChange={newValue => console.log(newValue)}
|
||||
>
|
||||
@ -37,4 +38,4 @@ export default function TabsShowcase() {
|
||||
</Tabs>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
35
platform/docs/src/pages/components/TooltipShowcase.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '../../../../ui-next/src/components/Tooltip';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import ShowcaseRow from './ShowcaseRow';
|
||||
|
||||
/**
|
||||
* TooltipShowcase component displays Tooltip variants and examples
|
||||
*/
|
||||
export default function TooltipShowcase() {
|
||||
return (
|
||||
<ShowcaseRow
|
||||
title="Tooltip"
|
||||
description="Tooltips reveal helper text when users hover, focus, or tap an element."
|
||||
code={`
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">?</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Tooltip content
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
`}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="icon">?</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Tooltip content
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</ShowcaseRow>
|
||||
);
|
||||
}
|
||||
@ -27,8 +27,9 @@ export default function ComponentShowcase() {
|
||||
description="Patterns and example layouts"
|
||||
>
|
||||
<div className="text-foreground min-h-screen bg-black">
|
||||
<div className="mx-auto my-4 max-w-5xl pt-6 pb-3">
|
||||
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
|
||||
<div className="mx-auto my-4 max-w-5xl pt-4 pb-6">
|
||||
{/* Navigation cards */}
|
||||
<div className="mb-8 grid grid-cols-1 gap-5 md:grid-cols-3">
|
||||
<a
|
||||
href="/colors-and-type"
|
||||
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
|
||||
@ -78,9 +79,7 @@ export default function ComponentShowcase() {
|
||||
</Card>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto my-4 max-w-5xl pt-4 pb-6">
|
||||
<h1 className="text-foreground ml-6 mb-6 text-5xl">Patterns</h1>
|
||||
|
||||
<ShowcaseRow
|
||||
|
||||
@ -3,7 +3,7 @@ import React from 'react';
|
||||
const DividerItem = () => {
|
||||
return (
|
||||
<div className="flex h-3.5 shrink-0 items-center px-2">
|
||||
<div className="bg-primary-dark h-[2px] w-full"></div>
|
||||
<div className="bg-background h-[2px] w-full"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
|
||||
import { ErrorBoundary as ReactErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import { Dialog, DialogContent } from '../Dialog/Dialog';
|
||||
import { ScrollArea } from '../ScrollArea/ScrollArea';
|
||||
import { Button } from '../Button/Button';
|
||||
import { useNotification } from '../../contextProviders';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
@ -112,7 +112,7 @@ interface ErrorBoundaryError extends Error {
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
interface DefaultFallbackProps {
|
||||
interface DefaultFallbackProps extends FallbackProps {
|
||||
error: ErrorBoundaryError;
|
||||
context: string;
|
||||
resetErrorBoundary: () => void;
|
||||
@ -135,6 +135,8 @@ const DefaultFallback = ({
|
||||
}: DefaultFallbackProps) => {
|
||||
const { t } = useTranslation('ErrorBoundary');
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const { show } = useNotification();
|
||||
|
||||
const title = `${t('Something went wrong')}${!isProduction && ` ${t('in')} ${context}`}.`;
|
||||
const subtitle = t('Sorry, something went wrong there. Try again.');
|
||||
|
||||
@ -143,20 +145,32 @@ const DefaultFallback = ({
|
||||
const copyErrorToClipboard = () => {
|
||||
if (code) {
|
||||
navigator.clipboard.writeText(code);
|
||||
toast.success(t('Error copied to clipboard'));
|
||||
show({
|
||||
title: t('Success'),
|
||||
message: t('Error copied to clipboard'),
|
||||
type: 'success',
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
toast.error(title, {
|
||||
description: subtitle,
|
||||
// Use a stable ID based on error message to support deduplication
|
||||
const errorId = `error-${errorTitle || error.message}`;
|
||||
|
||||
// We don't need to track shown state - instead rely on the notification deduplication system
|
||||
show({
|
||||
title,
|
||||
message: subtitle,
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
id: errorId,
|
||||
action: {
|
||||
label: t('Show Details'),
|
||||
onClick: () => setShowDetails(true),
|
||||
},
|
||||
duration: 0,
|
||||
});
|
||||
}, [error, subtitle, t, title]);
|
||||
}, [error, errorTitle, subtitle, t, title, show]);
|
||||
|
||||
if (isProduction) {
|
||||
return null;
|
||||
@ -286,9 +300,8 @@ const ErrorBoundary = ({
|
||||
<ReactErrorBoundary
|
||||
fallbackRender={props => (
|
||||
<FallbackComponent
|
||||
error={props.error}
|
||||
{...props}
|
||||
context={context}
|
||||
resetErrorBoundary={props.resetErrorBoundary}
|
||||
/>
|
||||
)}
|
||||
onReset={onResetHandler}
|
||||
|
||||
@ -14,7 +14,7 @@ export const Clear = (props: IconProps) => (
|
||||
fillRule="evenodd"
|
||||
>
|
||||
<circle
|
||||
fill="#0944B3"
|
||||
fill="currentColor"
|
||||
cx="9.5"
|
||||
cy="9.5"
|
||||
r="9.5"
|
||||
|
||||
@ -6,8 +6,8 @@ export const PowerOff = (props: IconProps) => (
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 28"
|
||||
aria-labelledby="title"
|
||||
width="1em"
|
||||
height="1em"
|
||||
width="28px"
|
||||
height="28px"
|
||||
fill="currentColor"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@ -67,9 +67,10 @@ function ViewportPane({
|
||||
|
||||
{/* Border overlay */}
|
||||
<div
|
||||
className={classNames('pointer-events-none absolute inset-0', {
|
||||
'border-highlight rounded-md border': isActive,
|
||||
'group-hover:border-highlight/50 rounded-md border border-transparent': !isActive,
|
||||
className={classNames('pointer-events-none absolute inset-0 rounded-md border', {
|
||||
'border-highlight': isActive,
|
||||
'group-hover/pane:border-highlight/50 border-transparent': !isActive,
|
||||
'!border-secondary-light border-dashed': isHighlighted,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
import React, { useState, createContext, useContext, useCallback, useEffect, useMemo } from 'react';
|
||||
import ManagedDialog, { ManagedDialogProps } from './ManagedDialog';
|
||||
import React, {
|
||||
useState,
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import ManagedDialog, { ManagedDialogProps, ManagedDialogRef } from './ManagedDialog';
|
||||
|
||||
interface DialogContextType {
|
||||
show: (options: ManagedDialogProps) => string;
|
||||
hide: (id: string) => void;
|
||||
hideAll: () => void;
|
||||
isEmpty: () => boolean;
|
||||
updatePosition: (id: string, position: { x: number; y: number }) => void;
|
||||
}
|
||||
|
||||
interface DialogService {
|
||||
@ -35,6 +44,7 @@ const DialogProvider: React.FC<DialogProviderProps> = ({
|
||||
service = null,
|
||||
}) => {
|
||||
const [dialogs, setDialogs] = useState<(ManagedDialogProps & { id: string })[]>([]);
|
||||
const dialogRefs = useRef<Map<string, ManagedDialogRef>>(new Map());
|
||||
|
||||
const show = useCallback((options: ManagedDialogProps) => {
|
||||
const id = options.id;
|
||||
@ -44,22 +54,32 @@ const DialogProvider: React.FC<DialogProviderProps> = ({
|
||||
|
||||
const hide = useCallback((id: string) => {
|
||||
setDialogs(prev => prev.filter(dialog => dialog.id !== id));
|
||||
dialogRefs.current.delete(id);
|
||||
}, []);
|
||||
|
||||
const hideAll = useCallback(() => {
|
||||
setDialogs([]);
|
||||
dialogRefs.current.clear();
|
||||
}, []);
|
||||
|
||||
const isEmpty = useCallback(() => dialogs.length === 0, [dialogs]);
|
||||
|
||||
const updatePosition = useCallback((id: string, position: { x: number; y: number }) => {
|
||||
const dialogRef = dialogRefs.current.get(id);
|
||||
if (dialogRef) {
|
||||
dialogRef.updatePosition(position);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
show,
|
||||
hide,
|
||||
hideAll,
|
||||
isEmpty,
|
||||
updatePosition,
|
||||
}),
|
||||
[show, hide, hideAll, isEmpty]
|
||||
[show, hide, hideAll, isEmpty, updatePosition]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -76,6 +96,11 @@ const DialogProvider: React.FC<DialogProviderProps> = ({
|
||||
{dialogs.map(dialog => (
|
||||
<RenderedDialog
|
||||
key={dialog.id}
|
||||
ref={(ref: ManagedDialogRef) => {
|
||||
if (ref) {
|
||||
dialogRefs.current.set(dialog.id, ref);
|
||||
}
|
||||
}}
|
||||
onClose={hide}
|
||||
isOpen={true}
|
||||
{...dialog}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect, useImperativeHandle, forwardRef } from 'react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../components/Dialog/Dialog';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@ -19,59 +19,85 @@ export interface ManagedDialogProps {
|
||||
containerClassName?: string;
|
||||
}
|
||||
|
||||
const ManagedDialog: React.FC<ManagedDialogProps> = ({
|
||||
id,
|
||||
isOpen,
|
||||
title,
|
||||
content: DialogContentComponent,
|
||||
contentProps,
|
||||
isDraggable,
|
||||
shouldCloseOnEsc = false,
|
||||
shouldCloseOnOverlayClick = false,
|
||||
showOverlay = true,
|
||||
defaultPosition,
|
||||
onClose,
|
||||
unstyled,
|
||||
containerClassName,
|
||||
}) => {
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
modal={false} // keep modal behavior off for independent windows
|
||||
onOpenChange={open => {
|
||||
if (!open) {
|
||||
onClose(id);
|
||||
}
|
||||
}}
|
||||
isDraggable={isDraggable}
|
||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
|
||||
showOverlay={showOverlay}
|
||||
>
|
||||
<DialogContent
|
||||
className={cn(unstyled ? 'p-0' : '', containerClassName)}
|
||||
unstyled={unstyled}
|
||||
style={{
|
||||
...(defaultPosition
|
||||
? {
|
||||
position: 'fixed',
|
||||
left: `${defaultPosition.x}px`,
|
||||
top: `${defaultPosition.y}px`,
|
||||
transform: 'translate(0, 0)',
|
||||
margin: 0,
|
||||
animation: 'none',
|
||||
}
|
||||
: {}),
|
||||
export interface ManagedDialogRef {
|
||||
updatePosition: (position: { x: number; y: number }) => void;
|
||||
}
|
||||
|
||||
const ManagedDialog = forwardRef<ManagedDialogRef, ManagedDialogProps>(
|
||||
(
|
||||
{
|
||||
id,
|
||||
isOpen,
|
||||
title,
|
||||
content: DialogContentComponent,
|
||||
contentProps,
|
||||
isDraggable,
|
||||
shouldCloseOnEsc = false,
|
||||
shouldCloseOnOverlayClick = false,
|
||||
showOverlay = true,
|
||||
defaultPosition,
|
||||
onClose,
|
||||
unstyled,
|
||||
containerClassName,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [currentPosition, setCurrentPosition] = useState(defaultPosition);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
updatePosition: (position: { x: number; y: number }) => {
|
||||
setCurrentPosition(position);
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPosition(defaultPosition);
|
||||
}, [defaultPosition]);
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
modal={false} // keep modal behavior off for independent windows
|
||||
onOpenChange={open => {
|
||||
if (!open) {
|
||||
onClose(id);
|
||||
}
|
||||
}}
|
||||
isDraggable={isDraggable}
|
||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
|
||||
showOverlay={showOverlay}
|
||||
>
|
||||
{!unstyled && <DialogHeader>{title && <DialogTitle>{title}</DialogTitle>}</DialogHeader>}
|
||||
<DialogContentComponent
|
||||
{...contentProps}
|
||||
hide={() => onClose(id)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
<DialogContent
|
||||
className={cn(unstyled ? 'p-0' : '', containerClassName)}
|
||||
unstyled={unstyled}
|
||||
style={{
|
||||
...(currentPosition
|
||||
? {
|
||||
position: 'fixed',
|
||||
left: `${currentPosition.x}px`,
|
||||
top: `${currentPosition.y}px`,
|
||||
transform: 'translate(0, 0)',
|
||||
margin: 0,
|
||||
animation: 'none',
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
{!unstyled && <DialogHeader>{title && <DialogTitle>{title}</DialogTitle>}</DialogHeader>}
|
||||
<DialogContentComponent
|
||||
{...contentProps}
|
||||
hide={() => onClose(id)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ManagedDialog.displayName = 'ManagedDialog';
|
||||
|
||||
export default ManagedDialog;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { createContext, useContext, useCallback, useEffect, ReactNode } from 'react';
|
||||
import React, { createContext, useContext, useCallback, useEffect, ReactNode, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Toaster, toast } from '../components';
|
||||
|
||||
@ -6,7 +6,23 @@ const NotificationContext = createContext(null);
|
||||
|
||||
export const useNotification = () => useContext(NotificationContext);
|
||||
|
||||
const NotificationProvider = ({ children, service }) => {
|
||||
// Notification deduplication cache
|
||||
interface NotificationCacheEntry {
|
||||
timestamp: number;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface NotificationProviderProps {
|
||||
children: ReactNode;
|
||||
service?: any;
|
||||
deduplicationInterval?: number;
|
||||
}
|
||||
|
||||
const NotificationProvider = ({
|
||||
children,
|
||||
service,
|
||||
deduplicationInterval = 10000, // Default to 10 seconds
|
||||
}: NotificationProviderProps) => {
|
||||
const DEFAULT_OPTIONS = {
|
||||
title: '',
|
||||
message: '',
|
||||
@ -15,39 +31,138 @@ const NotificationProvider = ({ children, service }) => {
|
||||
type: 'info', // info, success, error
|
||||
};
|
||||
|
||||
// Cache for recent notifications to prevent duplicates
|
||||
// Structure: { [title_message_type]: { timestamp, id } }
|
||||
const recentNotificationsRef = useRef<Record<string, NotificationCacheEntry>>({});
|
||||
|
||||
// Use the configurable deduplication interval from props
|
||||
|
||||
const show = useCallback(options => {
|
||||
const { title, message, duration, position, type, promise } = {
|
||||
const {
|
||||
title,
|
||||
message,
|
||||
duration,
|
||||
position,
|
||||
type,
|
||||
promise,
|
||||
allowDuplicates = false,
|
||||
deduplicationInterval: optionsDeduplicationInterval,
|
||||
action,
|
||||
} = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
};
|
||||
|
||||
// Use the provider's deduplicationInterval by default, but allow it to be overridden per notification
|
||||
const notificationDeduplicationInterval = optionsDeduplicationInterval || deduplicationInterval;
|
||||
|
||||
if (promise) {
|
||||
return toast.promise(promise, {
|
||||
loading: title || 'Loading...',
|
||||
success: (data: unknown) => ({
|
||||
title: title || 'Success',
|
||||
description: typeof message === 'function' ? message(data) : message,
|
||||
}),
|
||||
error: (err: unknown) => ({
|
||||
title: title || 'Error',
|
||||
description: typeof message === 'function' ? message(err) : message,
|
||||
}),
|
||||
success: (data: unknown) => {
|
||||
const description = typeof message === 'function' ? message(data) : message;
|
||||
return {
|
||||
title: title || 'Success',
|
||||
description,
|
||||
};
|
||||
},
|
||||
error: (err: unknown) => {
|
||||
const description = typeof message === 'function' ? message(err) : message;
|
||||
return {
|
||||
title: title || 'Error',
|
||||
description,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return toast[type](title, {
|
||||
// Create a cache key from notification properties
|
||||
const messageStr = typeof message === 'function' ? 'function' : message;
|
||||
const cacheKey = `${title}_${messageStr}_${type}`;
|
||||
|
||||
// Handle deduplication
|
||||
if (!allowDuplicates && type === 'error') {
|
||||
const now = Date.now();
|
||||
const cachedNotification = recentNotificationsRef.current[cacheKey];
|
||||
|
||||
// First check if we've shown this notification recently
|
||||
if (cachedNotification) {
|
||||
const timeSinceLastShown = now - cachedNotification.timestamp;
|
||||
|
||||
// If it's been shown recently and within the deduplication interval,
|
||||
// don't show it again
|
||||
if (timeSinceLastShown < notificationDeduplicationInterval) {
|
||||
console.debug(
|
||||
`Prevented duplicate notification: "${title}" (${timeSinceLastShown}ms < ${notificationDeduplicationInterval}ms)`
|
||||
);
|
||||
// Return the existing notification ID
|
||||
return cachedNotification.id;
|
||||
}
|
||||
|
||||
// If it's been shown before but outside the deduplication interval,
|
||||
// dismiss the old notification first and allow showing a new one
|
||||
console.debug(
|
||||
`Showing notification again after interval: "${title}" (${timeSinceLastShown}ms >= ${notificationDeduplicationInterval}ms)`
|
||||
);
|
||||
toast.dismiss(cachedNotification.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the notification with action if provided
|
||||
const toastOptions = {
|
||||
duration,
|
||||
position,
|
||||
description: message,
|
||||
});
|
||||
id: options.id, // Use provided ID if available
|
||||
};
|
||||
|
||||
// Add action button if provided
|
||||
if (action && action.label && typeof action.onClick === 'function') {
|
||||
toastOptions.action = {
|
||||
label: action.label,
|
||||
onClick: action.onClick,
|
||||
};
|
||||
}
|
||||
|
||||
const id = toast[type](title, toastOptions);
|
||||
|
||||
// Cache this notification for deduplication if it's an error
|
||||
if (type === 'error') {
|
||||
const timestamp = Date.now();
|
||||
recentNotificationsRef.current[cacheKey] = {
|
||||
timestamp,
|
||||
id,
|
||||
};
|
||||
|
||||
console.debug(
|
||||
`Stored notification in cache: "${title}" (id: ${id}, timestamp: ${timestamp})`
|
||||
);
|
||||
|
||||
// We no longer auto-delete the cache entry after duration
|
||||
// Instead, we keep it to track when the notification was last shown
|
||||
// The entry will be checked against the deduplication interval
|
||||
}
|
||||
|
||||
return id;
|
||||
}, []);
|
||||
|
||||
const hide = useCallback(id => {
|
||||
toast.dismiss(id);
|
||||
|
||||
// Remove from cache if present
|
||||
const cacheEntries = Object.entries(recentNotificationsRef.current);
|
||||
for (const [key, entry] of cacheEntries) {
|
||||
if (entry.id === id) {
|
||||
delete recentNotificationsRef.current[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const hideAll = useCallback(() => {
|
||||
toast.dismiss();
|
||||
// Clear notification cache
|
||||
recentNotificationsRef.current = {};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
@ -61,8 +176,28 @@ const NotificationProvider = ({ children, service }) => {
|
||||
}
|
||||
}, [service, hide, show]);
|
||||
|
||||
// Debug function to get the current cache (for development use)
|
||||
const getNotificationCache = useCallback(() => {
|
||||
const cache = { ...recentNotificationsRef.current };
|
||||
|
||||
// Add human-readable timestamps and time since showing
|
||||
const now = Date.now();
|
||||
const enhancedCache = Object.entries(cache).reduce((result, [key, entry]) => {
|
||||
const timeSince = now - entry.timestamp;
|
||||
result[key] = {
|
||||
...entry,
|
||||
date: new Date(entry.timestamp).toISOString(),
|
||||
timeSinceMs: timeSince,
|
||||
timeSinceStr: `${Math.floor(timeSince / 1000)}s ago`,
|
||||
};
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
return enhancedCache;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={{ show, hide, hideAll }}>
|
||||
<NotificationContext.Provider value={{ show, hide, hideAll, getNotificationCache }}>
|
||||
<Toaster position="bottom-right" />
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
@ -71,6 +206,8 @@ const NotificationProvider = ({ children, service }) => {
|
||||
|
||||
NotificationProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
service: PropTypes.object,
|
||||
deduplicationInterval: PropTypes.number,
|
||||
};
|
||||
|
||||
export const withNotification = Component => {
|
||||
|
||||
@ -104,7 +104,7 @@ export default defineConfig({
|
||||
// Copy public directory except config and html-templates
|
||||
{
|
||||
from: path.resolve(__dirname, 'node_modules/onnxruntime-web/dist'),
|
||||
to: DIST_DIR,
|
||||
to: `${DIST_DIR}/ort`,
|
||||
force: true,
|
||||
},
|
||||
{
|
||||
|
||||
|
Before Width: | Height: | Size: 327 KiB After Width: | Height: | Size: 327 KiB |
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 297 KiB |
@ -1 +1 @@
|
||||
3.10.1
|
||||
3.10.2
|
||||
|
||||
56
yarn.lock
@ -1393,10 +1393,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||
|
||||
"@cornerstonejs/adapters@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.10.1.tgz#a03c21d42c0f54b42b8886df54dec4823b867bd7"
|
||||
integrity sha512-uKic+EyF8SguEM9ahZK96r1oWetjO2GCCEM1oO15wl71iHVXFbW8lUYD1uqXtNW4fZet3X9rEke/te1zNVafxA==
|
||||
"@cornerstonejs/adapters@^3.11.6":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-3.11.6.tgz#a4ec9e2206e1956b2a1f524e08737a994ddd7f82"
|
||||
integrity sha512-EYLkBXfXF6UcmcZPitIkDNe02HdKykiFDnG9CtXPJkTSqFfCZnkF4Mqe4WN/husc9wia6BUMlESQRBYEgBithA==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
buffer "^6.0.3"
|
||||
@ -1404,10 +1404,10 @@
|
||||
gl-matrix "^3.4.3"
|
||||
ndarray "^1.0.19"
|
||||
|
||||
"@cornerstonejs/ai@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/ai/-/ai-3.10.1.tgz#795ca5e69d94a7b868a765484d2a9b5ed943ef33"
|
||||
integrity sha512-yEALz91yZCJdrRhk2R5Vn1yFefy07FL9g3ff4BgL19nTnGIu/3yUkVR9PY5js8T6E56GCFWxs9meWdeiCuRAcg==
|
||||
"@cornerstonejs/ai@^3.11.6":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/ai/-/ai-3.11.6.tgz#146bed97e452eb941013e437beb8b3416e412252"
|
||||
integrity sha512-F17IDzbn1PC2Zt1S2RS4OaLqO5ccC71Mmk2/9tU3wQS7g7qmyh8JzyB3B6t3iHkSlv20p3HNh8MqSug7zHOcrg==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.17.8"
|
||||
buffer "^6.0.3"
|
||||
@ -1443,20 +1443,20 @@
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
|
||||
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
|
||||
|
||||
"@cornerstonejs/core@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.10.1.tgz#074dd6412241ccb208b8615bb6a50db7527b0272"
|
||||
integrity sha512-DMlE98A5Oa81G9K5J4ZD1917SbJYteh+j4oJTY60KHAI7p0GoGQRV/ZcxaL+FS8mWng/qejVss9w4fLoLJcErg==
|
||||
"@cornerstonejs/core@^3.11.6":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-3.11.6.tgz#6d4192981af13911705914d6f5f0bd48303fc8a4"
|
||||
integrity sha512-8IlnDj7MZV/pfbtdF5gdpL53hLkriBgqcTY4hBIY4iW1A1plmwADsbVXzgyXl3Bi63aovmJhiQZ1VtQx0TzZGQ==
|
||||
dependencies:
|
||||
"@kitware/vtk.js" "32.12.1"
|
||||
comlink "^4.4.1"
|
||||
gl-matrix "^3.4.3"
|
||||
loglevel "^1.9.2"
|
||||
|
||||
"@cornerstonejs/dicom-image-loader@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.10.1.tgz#93744c03101135aa51324bce043ea1e40544ec5b"
|
||||
integrity sha512-w/55TI05KV3mP/RtL0ds+jfh19KlBZajJDgXystV489duvqJJUH27C+eZyrGFqbA3a+EDv4GYQa4jw/MDkGGqA==
|
||||
"@cornerstonejs/dicom-image-loader@^3.11.6":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-3.11.6.tgz#7abebb1b091fad6401a96df47bea08b38ba81fbf"
|
||||
integrity sha512-SJZ/LAw2gIyxzyQTEFWqBtyZELakqjcHOJnFxhkEmmpuJdpX4tgtV4k+6fnbKTg9OhybyP3prouMVPj5myjCZA==
|
||||
dependencies:
|
||||
"@cornerstonejs/codec-charls" "^1.2.3"
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
|
||||
@ -1468,25 +1468,25 @@
|
||||
pako "^2.0.4"
|
||||
uuid "^9.0.0"
|
||||
|
||||
"@cornerstonejs/labelmap-interpolation@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-3.10.1.tgz#fe5a6031ffabf916e70f1cc2edb2b45fc0ef5eee"
|
||||
integrity sha512-hcAR83rWA6lBYKXgYOWR/3ofhV3mqh4RRLbj8QnS9e//qtmF+UxoZ4I0125ihPkCjFqFOWuYgn42BnRZNhvjxA==
|
||||
"@cornerstonejs/labelmap-interpolation@^3.11.6":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-3.11.6.tgz#1969c44400067fa67eb6467c4adfb831149e85e8"
|
||||
integrity sha512-N55MxFxlRNXHVKpsJgeO8Z2aEURINxdmyPvGODGgYjZSOXZ+iR12Aly/iZHW9d/crq4GU440d5B6EmEZ6iuW8w==
|
||||
dependencies:
|
||||
"@itk-wasm/morphological-contour-interpolation" "1.1.0"
|
||||
itk-wasm "1.0.0-b.165"
|
||||
|
||||
"@cornerstonejs/polymorphic-segmentation@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-3.10.1.tgz#2ea4999bf10d4eb53a5fa2caca8ad4336ed9c9a9"
|
||||
integrity sha512-HErrnX7kDs5CVfcuPW5pyWw3lJovqqqKiIfPJdEmZNEM4CVSv9pNpYIbSX8uv42zWq+biQFkP4TwYw12q/r7Og==
|
||||
"@cornerstonejs/polymorphic-segmentation@^3.11.6":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-3.11.6.tgz#12a749443cdc2ea94a57f20fdae2c1377b6f0704"
|
||||
integrity sha512-dk+G12MqqmKSd2Rz8gHo1piEDAeijBcz1EAdF+50UQlUEds1fczHwFkMrh9mlHc9treyHi5+QEIr2MKRmwaJIw==
|
||||
dependencies:
|
||||
"@icr/polyseg-wasm" "0.4.0"
|
||||
|
||||
"@cornerstonejs/tools@^3.10.1":
|
||||
version "3.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.10.1.tgz#8a49c5f61eeb619f42247bd422100e8d5b09af18"
|
||||
integrity sha512-L1z8JiDwgbSwz040BSV5KBMMEe7IZyS4xYVhbn6cpfzCxSfevp53EkUZMhLl4bFcYyRPgXu5/KJO6f9wGFhEvA==
|
||||
"@cornerstonejs/tools@^3.11.6":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-3.11.6.tgz#63c051410d83c8ea5f68bb62b7691395e0890704"
|
||||
integrity sha512-Sp1QkEaA952GgZikTNzyj125FzpYtVAYbeqCOn0E3WS7K1yw2XPuRbSCTRkRcV/J66ZoeKl4KgZVrhDlxJGAnw==
|
||||
dependencies:
|
||||
"@types/offscreencanvas" "2019.7.3"
|
||||
comlink "^4.4.1"
|
||||
|
||||