chore: 3.11 Recovery 1 (#5514)
@ -15,22 +15,13 @@ defaults: &defaults
|
||||
commands:
|
||||
install_bun:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- bun-cache-v2-{{ arch }}-latest
|
||||
- run:
|
||||
name: Install Bun
|
||||
command: |
|
||||
if [ ! -d "$HOME/.bun" ]; then
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
fi
|
||||
curl -fsSL https://bun.sh/install | bash -s "bun-v1.2.23"
|
||||
echo 'export BUN_INSTALL="$HOME/.bun"' >> $BASH_ENV
|
||||
echo 'export PATH="$BUN_INSTALL/bin:$PATH"' >> $BASH_ENV
|
||||
source $BASH_ENV
|
||||
- save_cache:
|
||||
key: bun-cache-v2-{{ arch }}-latest
|
||||
paths:
|
||||
- ~/.bun
|
||||
|
||||
jobs:
|
||||
UNIT_TESTS:
|
||||
@ -121,6 +112,34 @@ jobs:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: ~/repo
|
||||
# SECURITY AUDIT
|
||||
- run:
|
||||
name: 'Security Audit - High Risk Vulnerabilities'
|
||||
command: |
|
||||
echo "🔍 Running bun audit for security vulnerabilities..."
|
||||
echo "Checking for HIGH-RISK vulnerabilities..."
|
||||
|
||||
if bun audit --audit-level high; then
|
||||
echo "✅ No high-risk vulnerabilities found"
|
||||
echo "🎉 Security audit passed!"
|
||||
else
|
||||
echo ""
|
||||
echo "❌ HIGH-RISK VULNERABILITIES DETECTED!"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "🔧 To fix these issues:"
|
||||
echo " 1. Run: bun audit"
|
||||
echo " 2. Review the vulnerability details"
|
||||
echo " 3. Update affected packages to secure versions"
|
||||
echo " 4. Test your changes"
|
||||
echo " 5. Re-run: bun audit --audit-level high"
|
||||
echo ""
|
||||
echo "📋 Full audit report:"
|
||||
bun audit --audit-level low || true
|
||||
echo ""
|
||||
echo "❌ This build cannot proceed until high-risk vulnerabilities are resolved."
|
||||
exit 1
|
||||
fi
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: bun install --frozen-lockfile
|
||||
@ -455,8 +474,13 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
# - HOLD_FOR_APPROVAL:
|
||||
# type: approval
|
||||
# requires:
|
||||
# - BUILD
|
||||
- NPM_PUBLISH:
|
||||
requires:
|
||||
# - HOLD_FOR_APPROVAL
|
||||
- BUILD
|
||||
- DOCKER_BETA_PUBLISH:
|
||||
requires:
|
||||
|
||||
2
.github/workflows/build-docs.yml
vendored
@ -23,6 +23,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.23
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20 # Or your desired Node version
|
||||
|
||||
4
.github/workflows/playwright.yml
vendored
@ -1,7 +1,7 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [master, release/*]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@ -18,6 +18,8 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.23
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
@ -10,6 +10,6 @@
|
||||
"deploy": "netlify deploy --prod --dir ./../platform/app/dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"netlify-cli": "^2.21.0"
|
||||
"netlify-cli": "2.21.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,8 +49,8 @@ RUN apt-get update && apt-get install -y build-essential python3
|
||||
|
||||
RUN mkdir /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
RUN npm install -g bun
|
||||
# RUN npm install -g lerna@7.4.2
|
||||
RUN npm install -g bun@1.2.23
|
||||
RUN npm install -g lerna@7.4.2
|
||||
ENV PATH=/usr/src/app/node_modules/.bin:$PATH
|
||||
|
||||
# Do an initial install and then a final install
|
||||
@ -60,6 +60,7 @@ COPY --parents ./addOns/package.json ./addOns/*/*/package.json ./extensions/*/pa
|
||||
|
||||
RUN bun pm cache rm
|
||||
RUN bun install
|
||||
RUN bun add ajv@8.12.0
|
||||
# Copy the local directory
|
||||
COPY --link --exclude=yarn.lock --exclude=package.json --exclude=Dockerfile . .
|
||||
|
||||
@ -98,7 +99,7 @@ COPY --from=builder /usr/src/app/platform/app/dist/dicom-microscopy-viewer /usr/
|
||||
# In entrypoint.sh, app-config.js might be overwritten, so chmod it to be writeable.
|
||||
# The nginx user cannot chmod it, so change to root.
|
||||
USER root
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html && chmod -R 777 /usr/share/nginx/html
|
||||
USER nginx
|
||||
ENTRYPOINT ["/usr/src/entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
132
addOns/externals/devDependencies/package.json
vendored
@ -9,85 +9,85 @@
|
||||
"yarn": ">=1.19.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"core-js": "^3.2.1",
|
||||
"moment": "^2.30.1"
|
||||
"clsx": "2.1.1",
|
||||
"core-js": "3.45.1",
|
||||
"moment": "2.30.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "7.28.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
"@rsbuild/core": "^1.3.16",
|
||||
"@rsbuild/plugin-node-polyfill": "^1.3.0",
|
||||
"@rsbuild/plugin-react": "^1.3.1",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@swc/helpers": "^0.5.15",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"babel-loader": "^8.2.4",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"dotenv": "^8.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.17",
|
||||
"@rsbuild/core": "1.5.1",
|
||||
"@rsbuild/plugin-node-polyfill": "1.4.2",
|
||||
"@rsbuild/plugin-react": "1.4.0",
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@swc/helpers": "0.5.17",
|
||||
"@types/jest": "27.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@typescript-eslint/parser": "6.21.0",
|
||||
"autoprefixer": "10.4.21",
|
||||
"babel-loader": "8.4.1",
|
||||
"babel-plugin-module-resolver": "5.0.2",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"copy-webpack-plugin": "9.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "6.11.0",
|
||||
"dotenv": "8.6.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"eslint-config-react-app": "^6.0.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-flowtype": "^7.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-config-react-app": "6.0.0",
|
||||
"eslint-plugin-cypress": "2.15.2",
|
||||
"eslint-plugin-flowtype": "7.0.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-prettier": "5.5.1",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.4.0",
|
||||
"eslint-plugin-tsdoc": "^0.2.11",
|
||||
"eslint-webpack-plugin": "^2.5.3",
|
||||
"execa": "^8.0.1",
|
||||
"extract-css-chunks-webpack-plugin": "^4.5.4",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"husky": "^3.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-canvas-mock": "^2.1.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jest-junit": "^6.4.0",
|
||||
"lerna": "^7.2.0",
|
||||
"lint-staged": "^9.0.2",
|
||||
"mini-css-extract-plugin": "^2.1.0",
|
||||
"optimize-css-assets-webpack-plugin": "^6.0.1",
|
||||
"postcss": "^8.3.5",
|
||||
"postcss-import": "^14.0.2",
|
||||
"postcss-loader": "^6.1.1",
|
||||
"postcss-preset-env": "^7.4.3",
|
||||
"eslint-plugin-promise": "5.2.0",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "4.6.2",
|
||||
"eslint-plugin-tsdoc": "0.2.17",
|
||||
"eslint-webpack-plugin": "2.7.0",
|
||||
"execa": "8.0.1",
|
||||
"extract-css-chunks-webpack-plugin": "4.10.0",
|
||||
"html-webpack-plugin": "5.6.3",
|
||||
"husky": "3.1.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-canvas-mock": "2.5.2",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-junit": "6.4.0",
|
||||
"lerna": "7.4.2",
|
||||
"lint-staged": "9.5.0",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"optimize-css-assets-webpack-plugin": "6.0.1",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-import": "14.1.0",
|
||||
"postcss-loader": "6.2.1",
|
||||
"postcss-preset-env": "7.8.3",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-tailwindcss": "0.6.9",
|
||||
"react-refresh": "^0.14.2",
|
||||
"semver": "^7.5.1",
|
||||
"serve": "^14.2.4",
|
||||
"shader-loader": "^1.3.1",
|
||||
"shx": "^0.3.3",
|
||||
"source-map-loader": "^4.0.1",
|
||||
"style-loader": "^1.0.0",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"react-refresh": "0.14.2",
|
||||
"semver": "7.7.2",
|
||||
"serve": "14.2.4",
|
||||
"shader-loader": "1.3.1",
|
||||
"shx": "0.3.4",
|
||||
"source-map-loader": "4.0.2",
|
||||
"style-loader": "1.3.0",
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"typescript": "5.5.4",
|
||||
"unused-webpack-plugin": "2.4.0",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-bundle-analyzer": "4.10.2",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.2.2",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"workbox-webpack-plugin": "^6.1.5",
|
||||
"worker-loader": "^3.0.8"
|
||||
"webpack-hot-middleware": "2.26.1",
|
||||
"webpack-merge": "5.10.0",
|
||||
"workbox-webpack-plugin": "6.6.1",
|
||||
"worker-loader": "3.0.8"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "Included as direct dependency"
|
||||
|
||||
@ -4,6 +4,6 @@
|
||||
"version": "3.11.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dicom-microscopy-viewer": "^0.46.1"
|
||||
"dicom-microscopy-viewer": "0.46.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,30 +22,29 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-transform-class-properties": "7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "7.27.1",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "7.28.1",
|
||||
"@babel/plugin-transform-runtime": "7.28.0",
|
||||
"@babel/plugin-transform-typescript": "^7.28.0",
|
||||
"@babel/plugin-transform-typescript": "7.28.0",
|
||||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.27.1"
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/@babel/runtime": "^7.20.13",
|
||||
"**/@babel/runtime": "7.28.2",
|
||||
"commander": "8.3.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicomweb-client": ">=0.10.4",
|
||||
"nth-check": "^2.1.1",
|
||||
"trim-newlines": "^5.0.0",
|
||||
"glob-parent": "^6.0.2",
|
||||
"trim": "^1.0.0",
|
||||
"package-json": "^8.1.0",
|
||||
"typescript": "5.5.4",
|
||||
"sharp": "^0.32.6"
|
||||
"nth-check": "2.1.1",
|
||||
"trim-newlines": "5.0.0",
|
||||
"glob-parent": "6.0.2",
|
||||
"trim": "1.0.1",
|
||||
"package-json": "8.1.1",
|
||||
"typescript": "5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,18 +37,18 @@
|
||||
"@ohif/extension-cornerstone": "3.11.0",
|
||||
"@ohif/extension-default": "3.11.0",
|
||||
"@ohif/i18n": "3.11.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-router": "^6.8.1",
|
||||
"react-router-dom": "^6.8.1"
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-router": "6.30.1",
|
||||
"react-router-dom": "6.30.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.5",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.13",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,15 +37,15 @@
|
||||
"@ohif/extension-cornerstone": "3.11.0",
|
||||
"@ohif/extension-default": "3.11.0",
|
||||
"@ohif/i18n": "3.11.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-router": "^6.23.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-router": "6.30.1",
|
||||
"react-router-dom": "6.30.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"react-color": "^2.19.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,18 +37,18 @@
|
||||
"@ohif/extension-cornerstone": "3.11.0",
|
||||
"@ohif/extension-default": "3.11.0",
|
||||
"@ohif/i18n": "3.11.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-router": "^6.23.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-router": "6.30.1",
|
||||
"react-router-dom": "6.30.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.5",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.13",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
"react-color": "2.19.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,16 +39,16 @@
|
||||
"@ohif/extension-measurement-tracking": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.5",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@cornerstonejs/tools": "^3.32.5",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.13",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@cornerstonejs/tools": "4.5.13",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,15 +35,15 @@
|
||||
"@ohif/i18n": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@cornerstonejs/tools": "^3.32.5",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@cornerstonejs/tools": "4.5.13",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,41 +31,43 @@
|
||||
"dev:cornerstone": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --config .webpack/webpack.prod.js",
|
||||
"build:package-1": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
"start": "yarn run dev",
|
||||
"test:unit": "jest --watchAll",
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@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.32.5",
|
||||
"@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.7",
|
||||
"@cornerstonejs/dicom-image-loader": "4.5.13",
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-resize-detector": "^10.0.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-resize-detector": "10.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.32.5",
|
||||
"@cornerstonejs/ai": "^3.32.5",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.32.5",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.32.5",
|
||||
"@cornerstonejs/tools": "^3.32.5",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/adapters": "4.5.13",
|
||||
"@cornerstonejs/ai": "4.5.13",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@cornerstonejs/labelmap-interpolation": "4.5.13",
|
||||
"@cornerstonejs/polymorphic-segmentation": "4.5.13",
|
||||
"@cornerstonejs/tools": "4.5.13",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lodash.compact": "^3.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.zip": "^4.2.0",
|
||||
"shader-loader": "^1.3.1",
|
||||
"worker-loader": "^3.0.8"
|
||||
"html2canvas": "1.4.1",
|
||||
"lodash.compact": "3.0.1",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.flatten": "4.4.0",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.zip": "4.2.0",
|
||||
"shader-loader": "1.3.1",
|
||||
"worker-loader": "3.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,10 +245,35 @@ function commandsModule({
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
storePositionPresentation(referencedDisplaySet);
|
||||
return commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
|
||||
const results = commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
|
||||
const disableEditing = customizationService.getCustomization(
|
||||
'panelSegmentation.disableEditing'
|
||||
);
|
||||
if (disableEditing) {
|
||||
const segmentationRepresentations = segmentationService.getSegmentationRepresentations(
|
||||
viewportId,
|
||||
{
|
||||
segmentationId: displaySet.displaySetInstanceUID,
|
||||
}
|
||||
);
|
||||
|
||||
segmentationRepresentations.forEach(representation => {
|
||||
const segmentIndices = Object.keys(representation.segments);
|
||||
segmentIndices.forEach(segmentIndex => {
|
||||
segmentationService.setSegmentLocked(
|
||||
representation.segmentationId,
|
||||
parseInt(segmentIndex),
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
return results;
|
||||
} else if (displaySet.Modality === 'SR') {
|
||||
const results = commandsManager.runCommand('hydrateStructuredReport', {
|
||||
displaySetInstanceUID: displaySet.displaySetInstanceUID,
|
||||
|
||||
@ -1754,7 +1754,10 @@ class SegmentationService extends PubSubService {
|
||||
return `${volumeLoaderSchema}:${displaySet.displaySetInstanceUID}`;
|
||||
}
|
||||
|
||||
private _getSegmentCenter(segmentationId, segmentIndex) {
|
||||
private _getSegmentCenter(
|
||||
segmentationId: string,
|
||||
segmentIndex: number
|
||||
): { image?: csTypes.Point3; world: csTypes.Point3 } | undefined {
|
||||
const segmentation = this.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
@ -1765,19 +1768,17 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
const { cachedStats } = segments[segmentIndex];
|
||||
|
||||
if (!cachedStats || !cachedStats?.center || !cachedStats?.namedStats?.center) {
|
||||
return;
|
||||
if (cachedStats?.center) {
|
||||
const { center } = cachedStats;
|
||||
|
||||
return center as { image: csTypes.Point3; world: csTypes.Point3 };
|
||||
}
|
||||
|
||||
const { center } = cachedStats;
|
||||
|
||||
if (!center) {
|
||||
if (cachedStats?.namedStats?.center) {
|
||||
return {
|
||||
world: cachedStats.namedStats.center.value,
|
||||
};
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
private _setSegmentLockedStatus(segmentationId: string, segmentIndex: number, isLocked: boolean) {
|
||||
|
||||
@ -37,19 +37,19 @@
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/i18n": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicomweb-client": "^0.10.4",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-window": "^1.8.9",
|
||||
"dicomweb-client": "0.10.4",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-window": "1.8.11",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/calculate-suv": "^1.1.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.uniqby": "^4.7.0"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/calculate-suv": "1.1.0",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.uniqby": "4.7.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,17 +262,19 @@ function createDicomJSONApi(dicomJsonConfig) {
|
||||
|
||||
const { StudyInstanceUID, SeriesInstanceUID } = displaySet;
|
||||
const study = findStudies('StudyInstanceUID', StudyInstanceUID)[0];
|
||||
const series = study.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID) || [];
|
||||
const series = study.series.find(s => s.SeriesInstanceUID === SeriesInstanceUID) || {};
|
||||
|
||||
const instanceMap = new Map();
|
||||
series.instances.forEach(instance => {
|
||||
if (instance?.metadata?.SOPInstanceUID) {
|
||||
const { metadata, url } = instance;
|
||||
const existingInstances = instanceMap.get(metadata.SOPInstanceUID) || [];
|
||||
existingInstances.push({ ...metadata, url });
|
||||
instanceMap.set(metadata.SOPInstanceUID, existingInstances);
|
||||
}
|
||||
});
|
||||
if (series.instances) {
|
||||
series.instances.forEach(instance => {
|
||||
if (instance?.metadata?.SOPInstanceUID) {
|
||||
const { metadata, url } = instance;
|
||||
const existingInstances = instanceMap.get(metadata.SOPInstanceUID) || [];
|
||||
existingInstances.push({ ...metadata, url });
|
||||
instanceMap.set(metadata.SOPInstanceUID, existingInstances);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
displaySet.images.forEach(instance => {
|
||||
const NumberOfFrames = instance.NumberOfFrames || 1;
|
||||
|
||||
@ -65,6 +65,11 @@ const commandsModule = ({
|
||||
* @param options.removeFirst - Optional flag to remove the display set first if it's already added
|
||||
*/
|
||||
addDisplaySetAsLayer: ({ viewportId, displaySetInstanceUID, removeFirst = false }) => {
|
||||
if (!viewportId) {
|
||||
const { activeViewportId } = servicesManager.services.viewportGridService.getState();
|
||||
viewportId = activeViewportId;
|
||||
}
|
||||
|
||||
if (!viewportId || !displaySetInstanceUID) {
|
||||
console.warn('Missing required parameters for addDisplaySetAsLayer command');
|
||||
return;
|
||||
|
||||
@ -8,31 +8,13 @@ export default {
|
||||
id: 'tagBrowser',
|
||||
label: 'Tag Browser',
|
||||
iconName: 'DicomTagBrowser',
|
||||
onClick: ({ commandsManager, displaySetInstanceUID }: withAppTypes) => {
|
||||
commandsManager.runCommand('openDICOMTagViewer', {
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
},
|
||||
commands: 'openDICOMTagViewer',
|
||||
},
|
||||
{
|
||||
id: 'addAsLayer',
|
||||
label: 'Add as Layer',
|
||||
iconName: 'ViewportViews',
|
||||
onClick: ({ commandsManager, displaySetInstanceUID, servicesManager }: withAppTypes) => {
|
||||
const { viewportGridService } = servicesManager.services;
|
||||
|
||||
// Get the active viewport
|
||||
const { activeViewportId } = viewportGridService.getState();
|
||||
if (!activeViewportId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the new command to add the display set as a layer
|
||||
commandsManager.runCommand('addDisplaySetAsLayer', {
|
||||
viewportId: activeViewportId,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
},
|
||||
commands: 'addDisplaySetAsLayer',
|
||||
},
|
||||
],
|
||||
'studyBrowser.sortFunctions': [
|
||||
|
||||
@ -34,20 +34,20 @@
|
||||
"@ohif/extension-default": "3.11.0",
|
||||
"@ohif/i18n": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-router": "^6.23.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-router": "6.30.1",
|
||||
"react-router-dom": "6.30.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",
|
||||
"colormap": "^2.3",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"mathjs": "^12.4.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/codec-charls": "1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "1.2.4",
|
||||
"colormap": "2.3.2",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"mathjs": "12.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,13 +33,13 @@
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,13 +33,13 @@
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,25 +32,25 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@cornerstonejs/tools": "^3.32.5",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@cornerstonejs/tools": "4.5.13",
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.11.0",
|
||||
"@ohif/extension-default": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"classnames": "^2.3.2",
|
||||
"classnames": "2.5.1",
|
||||
"dcmjs": "0.43.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"@xstate/react": "^3.2.2",
|
||||
"xstate": "^4.10.0"
|
||||
"@xstate/react": "3.2.2",
|
||||
"xstate": "4.38.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,13 +33,13 @@
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,13 +33,13 @@
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1"
|
||||
"dicom-parser": "1.8.21",
|
||||
"hammerjs": "2.0.8",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"classnames": "^2.3.2"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"classnames": "2.5.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,19 +31,19 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-router": "^6.23.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"prop-types": "15.8.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-router": "6.30.1",
|
||||
"react-router-dom": "6.30.1",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@cornerstonejs/tools": "^3.32.5",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@cornerstonejs/tools": "4.5.13",
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/extension-cornerstone": "3.11.0",
|
||||
"@ohif/extension-default": "3.11.0",
|
||||
@ -53,29 +53,29 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"@babel/eslint-parser": "7.28.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "7.27.1",
|
||||
"@babel/plugin-transform-class-properties": "7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "7.28.1",
|
||||
"@babel/plugin-transform-runtime": "7.28.0",
|
||||
"@babel/plugin-transform-typescript": "^7.28.0",
|
||||
"@babel/plugin-transform-typescript": "7.28.0",
|
||||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"babel-loader": "^8.2.4",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"babel-loader": "8.4.1",
|
||||
"babel-plugin-module-resolver": "5.0.2",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"copy-webpack-plugin": "9.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-loader": "2.2.1",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-cli": "^5.0.2",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,11 +39,11 @@
|
||||
"@ohif/extension-dicom-video": "3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"i18next": "17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "5.95.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,11 +44,11 @@
|
||||
"@ohif/extension-test": "3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"i18next": "17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "5.95.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,11 +45,11 @@
|
||||
"@ohif/extension-measurement-tracking": "3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"i18next": "17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "5.95.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
"@ohif/extension-dicom-microscopy": "3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"i18next": "17.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,10 +41,10 @@
|
||||
"@ohif/extension-tmtv": "3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13"
|
||||
"@babel/runtime": "7.28.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "5.95.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,33 +45,33 @@
|
||||
"@ohif/extension-dicom-video": "3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"i18next": "17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"@babel/eslint-parser": "7.28.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "7.27.1",
|
||||
"@babel/plugin-transform-class-properties": "7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "7.28.1",
|
||||
"@babel/plugin-transform-runtime": "7.28.0",
|
||||
"@babel/plugin-transform-typescript": "^7.28.0",
|
||||
"@babel/plugin-transform-typescript": "7.28.0",
|
||||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"babel-loader": "^8.0.0-beta.4",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"babel-loader": "8.4.1",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"copy-webpack-plugin": "9.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-loader": "2.2.1",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,11 +43,11 @@
|
||||
"@ohif/extension-measurement-tracking": "3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next": "^17.0.3"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"i18next": "17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "5.95.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,38 +33,38 @@
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.32.5",
|
||||
"@cornerstonejs/tools": "^3.32.5",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@cornerstonejs/tools": "4.5.13",
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "3.11.0",
|
||||
"@ohif/extension-ultrasound-pleura-bline": "3.11.0",
|
||||
"i18next": "^17.0.3"
|
||||
"i18next": "17.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"@babel/eslint-parser": "7.28.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "7.27.1",
|
||||
"@babel/plugin-transform-class-properties": "7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "7.28.1",
|
||||
"@babel/plugin-transform-runtime": "7.28.0",
|
||||
"@babel/plugin-transform-typescript": "^7.28.0",
|
||||
"@babel/plugin-transform-typescript": "7.28.0",
|
||||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"babel-loader": "^8.0.0-beta.4",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"babel-loader": "8.4.1",
|
||||
"clean-webpack-plugin": "3.0.0",
|
||||
"copy-webpack-plugin": "9.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-loader": "2.2.1",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-cli": "^5.0.2",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
90
package.json
@ -55,13 +55,13 @@
|
||||
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
||||
"test:e2e": "lerna run test:e2e --stream",
|
||||
"test:e2e:coverage": "nyc --reporter=html yarn run test:e2e:ci",
|
||||
"test:e2e:ci": "TEST_ENV=true npx playwright test",
|
||||
"test:e2e:ui": "TEST_ENV=true npx playwright test --ui",
|
||||
"test:e2e:reporter": "TEST_ENV=true npx playwright test --reporter=html",
|
||||
"test:e2e:update": "TEST_ENV=true npx playwright test --reporter=html --update-snapshots -g shouldUpdateThis",
|
||||
"test:e2e:update:debug": "TEST_ENV=true npx playwright test -g @debug --reporter=html --update-snapshots -g shouldUpdateThis",
|
||||
"test:e2e:headed": "TEST_ENV=true npx playwright test --headed",
|
||||
"test:e2e:debug": "TEST_ENV=true npx playwright test --debug",
|
||||
"test:e2e:ci": "cross-env TEST_ENV=true npx playwright test",
|
||||
"test:e2e:ui": "cross-env TEST_ENV=true npx playwright test --ui",
|
||||
"test:e2e:reporter": "cross-env TEST_ENV=true npx playwright test --reporter=html",
|
||||
"test:e2e:update": "cross-env TEST_ENV=true npx playwright test --reporter=html --update-snapshots -g shouldUpdateThis",
|
||||
"test:e2e:update:debug": "cross-env TEST_ENV=true npx playwright test -g @debug --reporter=html --update-snapshots -g shouldUpdateThis",
|
||||
"test:e2e:headed": "cross-env TEST_ENV=true npx playwright test --headed",
|
||||
"test:e2e:debug": "cross-env TEST_ENV=true npx playwright test --debug",
|
||||
"test:e2e:dist": "lerna run test:e2e:dist --stream",
|
||||
"test:e2e:serve": "yarn test:data && lerna run test:e2e:serve --stream",
|
||||
"see-changed": "lerna changed",
|
||||
@ -75,39 +75,50 @@
|
||||
"link-list": "npm ls --depth=0 --link=true"
|
||||
},
|
||||
"dependencies": {
|
||||
"execa": "^8.0.1"
|
||||
"execa": "8.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@percy/cypress": "^3.1.1",
|
||||
"@playwright/test": "^1.48.0"
|
||||
"@percy/cypress": "3.1.6",
|
||||
"@playwright/test": "1.56.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"@babel/plugin-transform-class-properties": "^7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-for-of": "^7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-syntax-dynamic-import": "7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "7.27.1",
|
||||
"@babel/plugin-transform-class-properties": "7.27.1",
|
||||
"@babel/plugin-transform-for-of": "7.27.1",
|
||||
"@babel/plugin-transform-object-rest-spread": "7.28.0",
|
||||
"@babel/plugin-transform-private-methods": "7.27.1",
|
||||
"@babel/plugin-transform-private-property-in-object": "7.27.1",
|
||||
"@babel/plugin-transform-regenerator": "7.28.1",
|
||||
"@babel/plugin-transform-runtime": "7.28.0",
|
||||
"@babel/plugin-transform-typescript": "^7.28.0",
|
||||
"@babel/plugin-transform-typescript": "7.28.0",
|
||||
"@babel/preset-env": "7.28.0",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"babel-plugin-istanbul": "^7.0.0",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@istanbuljs/nyc-config-typescript": "1.0.2",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.17",
|
||||
"babel-loader": "8.4.1",
|
||||
"babel-plugin-istanbul": "7.0.0",
|
||||
"babel-plugin-transform-import-meta": "2.3.3",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "6.11.0",
|
||||
"cypress": "14.5.2",
|
||||
"cypress-file-upload": "^5.0.8",
|
||||
"cypress-file-upload": "5.0.8",
|
||||
"file-loader": "6.2.0",
|
||||
"istanbul": "0.4.5",
|
||||
"nyc": "^17.1.0",
|
||||
"patch-package": "^8.0.0",
|
||||
"playwright-test-coverage": "^1.2.12",
|
||||
"prettier": "^3.3.3",
|
||||
"nyc": "17.1.0",
|
||||
"patch-package": "8.0.0",
|
||||
"playwright-test-coverage": "1.2.12",
|
||||
"postcss-loader": "6.2.1",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-tailwindcss": "0.6.9",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-node": "^10.9.2"
|
||||
"shader-loader": "1.3.1",
|
||||
"source-map-support": "0.5.21",
|
||||
"style-loader": "1.3.0",
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-node": "10.9.2",
|
||||
"worker-loader": "3.0.8"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@ -122,17 +133,22 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"commander": "8.3.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cross-spawn": "7.0.6",
|
||||
"dcmjs": "0.43.1",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"nth-check": "^2.1.1",
|
||||
"trim-newlines": "^5.0.0",
|
||||
"glob-parent": "^6.0.2",
|
||||
"trim": "^1.0.0",
|
||||
"package-json": "^8.1.0",
|
||||
"sharp": "^0.32.6",
|
||||
"nth-check": "2.1.1",
|
||||
"trim-newlines": "5.0.0",
|
||||
"glob-parent": "6.0.2",
|
||||
"trim": "1.0.1",
|
||||
"package-json": "8.1.1",
|
||||
"rollup": "2.79.2",
|
||||
"body-parser": "1.20.3",
|
||||
"form-data": ">=4.0.4"
|
||||
"form-data": ">=4.0.4",
|
||||
"axios": "1.12.0",
|
||||
"core-js": "3.45.1",
|
||||
"@babel/runtime-corejs2": "7.26.10",
|
||||
"tapable": "2.2.2"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@ -48,13 +48,13 @@
|
||||
"README.md"
|
||||
],
|
||||
"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.32.5",
|
||||
"@emotion/serialize": "^1.1.3",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"@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.7",
|
||||
"@cornerstonejs/dicom-image-loader": "4.5.13",
|
||||
"@emotion/serialize": "1.3.3",
|
||||
"@ohif/core": "3.11.0",
|
||||
"@ohif/extension-cornerstone": "3.11.0",
|
||||
"@ohif/extension-cornerstone-dicom-rt": "3.11.0",
|
||||
@ -74,41 +74,52 @@
|
||||
"@ohif/mode-ultrasound-pleura-bline": "3.11.0",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"@ohif/ui-next": "3.11.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"classnames": "^2.3.2",
|
||||
"core-js": "*",
|
||||
"cornerstone-math": "^0.1.9",
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@types/react": "18.3.23",
|
||||
"classnames": "2.5.1",
|
||||
"core-js": "3.45.1",
|
||||
"cornerstone-math": "0.1.10",
|
||||
"dcmjs": "0.43.1",
|
||||
"detect-gpu": "^4.0.16",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"dotenv-webpack": "^1.7.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"detect-gpu": "4.0.50",
|
||||
"dicom-parser": "1.8.21",
|
||||
"dotenv-webpack": "1.8.0",
|
||||
"file-loader": "6.2.0",
|
||||
"hammerjs": "2.0.8",
|
||||
"history": "5.3.0",
|
||||
"i18next": "17.3.1",
|
||||
"i18next-browser-languagedetector": "3.1.1",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"oidc-client": "1.11.5",
|
||||
"oidc-client-ts": "^3.0.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "^6.12.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^10.1.7",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-resize-detector": "^10.0.1",
|
||||
"react-router": "^6.23.1",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"oidc-client-ts": "3.3.0",
|
||||
"prop-types": "15.8.1",
|
||||
"query-string": "6.14.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-dropzone": "10.2.2",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-resize-detector": "10.0.1",
|
||||
"react-router": "6.30.1",
|
||||
"react-router-dom": "6.30.1",
|
||||
"react-shepherd": "6.1.1",
|
||||
"shepherd.js": "13.0.3",
|
||||
"url-loader": "^4.1.1",
|
||||
"url-loader": "4.1.1",
|
||||
"zustand": "4.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-private-methods": "^7.27.1",
|
||||
"@types/node": "^20.12.12",
|
||||
"@babel/plugin-transform-private-methods": "7.27.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.17",
|
||||
"@types/node": "20.19.9",
|
||||
"clean-webpack-plugin": "4.0.0",
|
||||
"copy-webpack-plugin": "10.2.4",
|
||||
"cross-env": "7.0.3",
|
||||
"extract-css-chunks-webpack-plugin": "4.10.0",
|
||||
"html-webpack-plugin": "5.6.3",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
"tailwindcss": "3.2.4"
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"shx": "0.3.4",
|
||||
"tailwindcss": "3.2.4",
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"webpack-dev-server": "5.2.2",
|
||||
"workbox-webpack-plugin": "6.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ window.config = {
|
||||
// above, the number of requests can be go a lot higher.
|
||||
prefetch: 25,
|
||||
},
|
||||
showErrorDetails: 'always', // 'always', 'dev', 'production'
|
||||
// filterQueryParam: false,
|
||||
// Defines multi-monitor layouts
|
||||
multimonitor: [
|
||||
|
||||
@ -128,11 +128,17 @@ const createRoutes = ({
|
||||
];
|
||||
|
||||
function RouteWithErrorBoundary({ route, ...rest }) {
|
||||
const [appConfig] = useAppConfig();
|
||||
const { showErrorDetails } = appConfig;
|
||||
|
||||
history.navigate = useNavigate();
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return (
|
||||
<ErrorBoundary context={`Route ${route.path}`}>
|
||||
<ErrorBoundary
|
||||
context={`Route ${route.path}`}
|
||||
showErrorDetails={showErrorDetails}
|
||||
>
|
||||
<route.children
|
||||
{...rest}
|
||||
{...route.props}
|
||||
|
||||
@ -21,20 +21,20 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"axios": "^1.11.0",
|
||||
"chalk": "^5.0.0",
|
||||
"execa": "^8.0.1",
|
||||
"gitignore": "^0.7.0",
|
||||
"inquirer": "^8.2.0",
|
||||
"listr": "^0.14.3",
|
||||
"mustache": "^4.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"node-fetch": "^3.1.1",
|
||||
"pkg-install": "^1.0.0",
|
||||
"registry-url": "^6.0.0",
|
||||
"spdx-license-list": "^6.4.0",
|
||||
"util": "^0.12.4",
|
||||
"yarn-programmatic": "^0.1.2"
|
||||
"axios": "1.12.0",
|
||||
"chalk": "5.4.1",
|
||||
"execa": "8.0.1",
|
||||
"gitignore": "0.7.0",
|
||||
"inquirer": "8.2.6",
|
||||
"listr": "0.14.3",
|
||||
"mustache": "4.2.0",
|
||||
"ncp": "2.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"pkg-install": "1.0.0",
|
||||
"registry-url": "6.0.1",
|
||||
"spdx-license-list": "6.10.0",
|
||||
"util": "0.12.5",
|
||||
"yarn-programmatic": "0.1.2"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
|
||||
@ -33,33 +33,33 @@
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@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/core": "^3.32.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.32.5",
|
||||
"@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.7",
|
||||
"@cornerstonejs/core": "4.5.13",
|
||||
"@cornerstonejs/dicom-image-loader": "4.5.13",
|
||||
"@ohif/ui": "3.11.0",
|
||||
"cornerstone-math": "0.1.9",
|
||||
"dicom-parser": "^1.8.21"
|
||||
"dicom-parser": "1.8.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@babel/runtime": "7.28.2",
|
||||
"dcmjs": "0.43.1",
|
||||
"dicomweb-client": "^0.10.4",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"moment": "^2.30.1",
|
||||
"dicomweb-client": "0.10.4",
|
||||
"gl-matrix": "3.4.3",
|
||||
"immutability-helper": "3.1.1",
|
||||
"isomorphic-base64": "1.0.2",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"moment": "2.30.1",
|
||||
"object-hash": "2.1.1",
|
||||
"query-string": "^6.14.0",
|
||||
"query-string": "6.14.1",
|
||||
"react-shepherd": "6.1.1",
|
||||
"shepherd.js": "13.0.3",
|
||||
"validate.js": "^0.12.0"
|
||||
"validate.js": "0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-merge": "*"
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,6 +196,7 @@ if auth headers are used, a preflight request is required.
|
||||
- `activateViewportBeforeInteraction`: (default to true), if set to false, tools can be used directly without the need to click and activate the viewport.
|
||||
- `autoPlayCine`: (default to false), if set to true, data sets with the DICOM frame time tag (i.e. (0018,1063)) will auto play when displayed
|
||||
- `addWindowLevelActionMenu`: (default to true), if set to false, the window level action menu item is NOT added to the viewport action corners
|
||||
- `showErrorDetails`: determines which runtime environments can display exception and error details caught at the `ErrorBoundary`; acceptable values include: `always`, `dev`, and `production`
|
||||
- `dangerouslyUseDynamicConfig`: Dynamic config allows user to pass `configUrl` query string. This allows to load config without recompiling application. If the `configUrl` query string is passed, the worklist and modes will load from the referenced json rather than the default .env config. If there is no `configUrl` path provided, the default behaviour is used and there should not be any deviation from current user experience.<br/>
|
||||
Points to consider while using `dangerouslyUseDynamicConfig`:<br/>
|
||||
- User have to enable this feature by setting `dangerouslyUseDynamicConfig.enabled:true`. By default it is `false`.
|
||||
|
||||
@ -48,52 +48,52 @@
|
||||
"@docusaurus/tsconfig": "3.0.0",
|
||||
"@docusaurus/types": "3.0.0",
|
||||
"@mdx-js/react": "3.0.1",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.1.6",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slider": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@types/react": "^18.2.29",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"classnames": "^2.3.2",
|
||||
"clsx": "^1.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"docusaurus-plugin-image-zoom": "^1.0.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"@radix-ui/react-accordion": "1.2.11",
|
||||
"@radix-ui/react-checkbox": "1.3.2",
|
||||
"@radix-ui/react-dialog": "1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.15",
|
||||
"@radix-ui/react-hover-card": "1.1.14",
|
||||
"@radix-ui/react-icons": "1.3.2",
|
||||
"@radix-ui/react-label": "2.1.7",
|
||||
"@radix-ui/react-popover": "1.1.14",
|
||||
"@radix-ui/react-scroll-area": "1.2.9",
|
||||
"@radix-ui/react-select": "2.2.5",
|
||||
"@radix-ui/react-separator": "1.1.7",
|
||||
"@radix-ui/react-slider": "1.3.5",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-switch": "1.2.5",
|
||||
"@radix-ui/react-tabs": "1.1.12",
|
||||
"@radix-ui/react-toggle": "1.1.9",
|
||||
"@radix-ui/react-tooltip": "1.2.7",
|
||||
"@svgr/webpack": "8.1.0",
|
||||
"@types/react": "18.3.23",
|
||||
"autoprefixer": "10.4.21",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"classnames": "2.5.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"date-fns": "3.6.0",
|
||||
"docusaurus-plugin-image-zoom": "1.0.1",
|
||||
"file-loader": "6.2.0",
|
||||
"framer-motion": "6.2.4",
|
||||
"glob": "^10.3.10",
|
||||
"lucide-react": "^0.379.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-import": "^14.0.2",
|
||||
"postcss-preset-env": "^7.4.3",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-outside-click-handler": "^1.3.0",
|
||||
"glob": "10.4.5",
|
||||
"lucide-react": "0.379.0",
|
||||
"next-themes": "0.3.0",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-import": "14.1.0",
|
||||
"postcss-preset-env": "7.8.3",
|
||||
"prism-react-renderer": "2.1.0",
|
||||
"react": "18.3.1",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-outside-click-handler": "1.3.0",
|
||||
"react-shepherd": "6.1.1",
|
||||
"shepherd.js": "13.0.3",
|
||||
"sonner": "^1.4.41",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "~5.2.2",
|
||||
"url-loader": "^4.1.1"
|
||||
"sonner": "1.7.4",
|
||||
"tailwind-merge": "2.6.0",
|
||||
"tailwindcss": "3.2.4",
|
||||
"tailwindcss-animate": "1.0.7",
|
||||
"typescript": "5.5.4",
|
||||
"url-loader": "4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,24 +31,24 @@
|
||||
"test:unit:ci": "echo 'platform/i18n: missing unit tests'"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2"
|
||||
"i18next": "17.3.1",
|
||||
"i18next-browser-languagedetector": "3.1.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"i18next-locize-backend": "^2.0.0",
|
||||
"locize-editor": "^2.0.0",
|
||||
"locize-lastused": "^1.1.0"
|
||||
"@babel/runtime": "7.28.2",
|
||||
"i18next-locize-backend": "2.2.2",
|
||||
"locize-editor": "2.2.2",
|
||||
"locize-lastused": "1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"i18next": "17.3.1",
|
||||
"i18next-browser-languagedetector": "3.1.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-i18next": "12.3.1",
|
||||
"webpack-merge": "5.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rm -rf node_modules/.cache/storybook && shx rm -rf dist",
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"start": "yarn run build --watch",
|
||||
"dev": "cross-env NODE_ENV=development webpack serve --config .webpack/webpack.playground.js",
|
||||
@ -27,43 +27,44 @@
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.1",
|
||||
"@radix-ui/react-context-menu": "^2.2.4",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
"@radix-ui/react-hover-card": "^1.1.6",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-slider": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "*",
|
||||
"cmdk": "^1.0.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"@radix-ui/react-accordion": "1.2.11",
|
||||
"@radix-ui/react-checkbox": "1.3.2",
|
||||
"@radix-ui/react-context-menu": "2.2.15",
|
||||
"@radix-ui/react-dialog": "1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.15",
|
||||
"@radix-ui/react-hover-card": "1.1.14",
|
||||
"@radix-ui/react-icons": "1.3.2",
|
||||
"@radix-ui/react-label": "2.1.7",
|
||||
"@radix-ui/react-popover": "1.1.14",
|
||||
"@radix-ui/react-scroll-area": "1.2.9",
|
||||
"@radix-ui/react-select": "2.2.5",
|
||||
"@radix-ui/react-separator": "1.1.7",
|
||||
"@radix-ui/react-slider": "1.3.5",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-switch": "1.2.5",
|
||||
"@radix-ui/react-tabs": "1.1.12",
|
||||
"@radix-ui/react-toggle": "1.1.9",
|
||||
"@radix-ui/react-toggle-group": "1.1.10",
|
||||
"@radix-ui/react-tooltip": "1.2.7",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"date-fns": "3.6.0",
|
||||
"framer-motion": "6.2.4",
|
||||
"lucide-react": "^0.379.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"react": "^18.3.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"lucide-react": "0.379.0",
|
||||
"next-themes": "0.3.0",
|
||||
"react": "18.3.1",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-resizable-panels": "2.1.9",
|
||||
"react-shepherd": "6.1.1",
|
||||
"shepherd.js": "13.0.3",
|
||||
"sonner": "^1.5.0",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"sonner": "1.7.4",
|
||||
"tailwind-merge": "2.6.0",
|
||||
"tailwindcss": "3.2.4",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"tailwindcss-animate": "1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-private-property-in-object": "^7.27.1"
|
||||
"@babel/plugin-transform-private-property-in-object": "7.27.1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "OHIF",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Button } from '../../components/Button/Button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -81,292 +81,291 @@ interface DataRowProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const DataRow: React.FC<DataRowProps> = ({
|
||||
number,
|
||||
title,
|
||||
colorHex,
|
||||
details,
|
||||
onSelect,
|
||||
isLocked,
|
||||
onToggleVisibility,
|
||||
onToggleLocked,
|
||||
onRename,
|
||||
onDelete,
|
||||
onColor,
|
||||
isSelected = false,
|
||||
isVisible = true,
|
||||
disableEditing = false,
|
||||
className,
|
||||
}) => {
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const isTitleLong = title?.length > 25;
|
||||
const rowRef = useRef<HTMLDivElement>(null);
|
||||
const DataRow = React.forwardRef<HTMLDivElement, DataRowProps>(
|
||||
(
|
||||
{
|
||||
number,
|
||||
title,
|
||||
colorHex,
|
||||
details,
|
||||
onSelect,
|
||||
isLocked,
|
||||
onToggleVisibility,
|
||||
onToggleLocked,
|
||||
onRename,
|
||||
onDelete,
|
||||
onColor,
|
||||
isSelected = false,
|
||||
isVisible = true,
|
||||
disableEditing = false,
|
||||
className,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const isTitleLong = title?.length > 25;
|
||||
|
||||
// useEffect(() => {
|
||||
// if (isSelected && rowRef.current) {
|
||||
// setTimeout(() => {
|
||||
// rowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// }, 200);
|
||||
// }
|
||||
// }, [isSelected]);
|
||||
const handleAction = (action: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
switch (action) {
|
||||
case 'Rename':
|
||||
onRename(e);
|
||||
break;
|
||||
case 'Lock':
|
||||
onToggleLocked(e);
|
||||
break;
|
||||
case 'Delete':
|
||||
onDelete(e);
|
||||
break;
|
||||
case 'Color':
|
||||
onColor(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAction = (action: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
switch (action) {
|
||||
case 'Rename':
|
||||
onRename(e);
|
||||
break;
|
||||
case 'Lock':
|
||||
onToggleLocked(e);
|
||||
break;
|
||||
case 'Delete':
|
||||
onDelete(e);
|
||||
break;
|
||||
case 'Color':
|
||||
onColor(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
const decodeHTML = (html: string) => {
|
||||
const txt = document.createElement('textarea');
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
};
|
||||
|
||||
const decodeHTML = (html: string) => {
|
||||
const txt = document.createElement('textarea');
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
};
|
||||
|
||||
const renderDetailText = (text: string, indent: number = 0) => {
|
||||
const indentation = ' '.repeat(indent);
|
||||
if (text === '') {
|
||||
const renderDetailText = (text: string, indent: number = 0) => {
|
||||
const indentation = ' '.repeat(indent);
|
||||
if (text === '') {
|
||||
return (
|
||||
<div
|
||||
key={`empty-${indent}`}
|
||||
className="h-2"
|
||||
></div>
|
||||
);
|
||||
}
|
||||
const cleanText = decodeHTML(text);
|
||||
return (
|
||||
<div
|
||||
key={`empty-${indent}`}
|
||||
className="h-2"
|
||||
></div>
|
||||
key={cleanText}
|
||||
className="whitespace-pre-wrap"
|
||||
>
|
||||
{indentation}
|
||||
<span className="font-medium">{cleanText}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const cleanText = decodeHTML(text);
|
||||
return (
|
||||
<div
|
||||
key={cleanText}
|
||||
className="whitespace-pre-wrap"
|
||||
>
|
||||
{indentation}
|
||||
<span className="font-medium">{cleanText}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const renderDetails = (details: string[]) => {
|
||||
const visibleLines = details.slice(0, 4);
|
||||
const hiddenLines = details.slice(4);
|
||||
const renderDetails = (details: string[]) => {
|
||||
const visibleLines = details.slice(0, 4);
|
||||
const hiddenLines = details.slice(4);
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="cursor-help">
|
||||
<div className="flex flex-col space-y-1">
|
||||
{visibleLines.map((line, lineIndex) =>
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="cursor-help">
|
||||
<div className="flex flex-col space-y-1">
|
||||
{visibleLines.map((line, lineIndex) =>
|
||||
renderDetailText(line, line.startsWith(' ') ? 1 : 0)
|
||||
)}
|
||||
</div>
|
||||
{hiddenLines.length > 0 && (
|
||||
<div className="text-muted-foreground mt-1 flex items-center text-sm">
|
||||
<span>...</span>
|
||||
<Icons.Info className="mr-1 h-5 w-5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="right"
|
||||
align="start"
|
||||
className="max-w-md"
|
||||
>
|
||||
<div className="text-secondary-foreground flex flex-col space-y-1 text-sm leading-normal">
|
||||
{details.map((line, lineIndex) =>
|
||||
renderDetailText(line, line.startsWith(' ') ? 1 : 0)
|
||||
)}
|
||||
</div>
|
||||
{hiddenLines.length > 0 && (
|
||||
<div className="text-muted-foreground mt-1 flex items-center text-sm">
|
||||
<span>...</span>
|
||||
<Icons.Info className="mr-1 h-5 w-5" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="right"
|
||||
align="start"
|
||||
className="max-w-md"
|
||||
>
|
||||
<div className="text-secondary-foreground flex flex-col space-y-1 text-sm leading-normal">
|
||||
{details.map((line, lineIndex) =>
|
||||
renderDetailText(line, line.startsWith(' ') ? 1 : 0)
|
||||
)}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={rowRef}
|
||||
className={cn('flex flex-col', !isVisible && 'opacity-60', className)}
|
||||
>
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center ${
|
||||
isSelected ? 'bg-popover' : 'bg-muted'
|
||||
} group relative cursor-pointer`}
|
||||
onClick={onSelect}
|
||||
data-cy="data-row"
|
||||
ref={ref}
|
||||
className={cn('flex flex-col', !isVisible && 'opacity-60', className)}
|
||||
>
|
||||
{/* Hover Overlay */}
|
||||
<div className="bg-primary/20 pointer-events-none absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100"></div>
|
||||
<div
|
||||
className={`flex items-center ${
|
||||
isSelected ? 'bg-popover' : 'bg-muted'
|
||||
} group relative cursor-pointer`}
|
||||
onClick={onSelect}
|
||||
data-cy="data-row"
|
||||
>
|
||||
{/* Hover Overlay */}
|
||||
<div className="bg-primary/20 pointer-events-none absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100"></div>
|
||||
|
||||
{/* Number Box */}
|
||||
{number !== null && (
|
||||
<div
|
||||
className={`flex h-7 max-h-7 w-7 flex-shrink-0 items-center justify-center rounded-l border-r border-black text-base ${
|
||||
isSelected ? 'bg-highlight text-black' : 'bg-muted text-muted-foreground'
|
||||
} overflow-hidden`}
|
||||
>
|
||||
{number}
|
||||
</div>
|
||||
)}
|
||||
{/* Number Box */}
|
||||
{number !== null && (
|
||||
<div
|
||||
className={`flex h-7 max-h-7 w-7 flex-shrink-0 items-center justify-center rounded-l border-r border-black text-base ${
|
||||
isSelected ? 'bg-highlight text-black' : 'bg-muted text-muted-foreground'
|
||||
} overflow-hidden`}
|
||||
>
|
||||
{number}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* add some space if there is not segment index */}
|
||||
{number === null && <div className="ml-1 h-7"></div>}
|
||||
{colorHex && (
|
||||
<div className="flex h-7 w-5 items-center justify-center">
|
||||
<span
|
||||
className="ml-2 h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: colorHex }}
|
||||
></span>
|
||||
</div>
|
||||
)}
|
||||
{/* add some space if there is not segment index */}
|
||||
{number === null && <div className="ml-1 h-7"></div>}
|
||||
{colorHex && (
|
||||
<div className="flex h-7 w-5 items-center justify-center">
|
||||
<span
|
||||
className="ml-2 h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: colorHex }}
|
||||
></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Label with Conditional Tooltip */}
|
||||
<div className="ml-2 flex-1 overflow-hidden">
|
||||
{isTitleLong ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className={`cursor-default text-base ${
|
||||
isSelected ? 'text-highlight' : 'text-muted-foreground'
|
||||
} [overflow:hidden] [display:-webkit-box] [-webkit-line-clamp:2] [-webkit-box-orient:vertical]`}
|
||||
{/* Label with Conditional Tooltip */}
|
||||
<div className="ml-2 flex-1 overflow-hidden">
|
||||
{isTitleLong ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
className={`cursor-default text-base ${
|
||||
isSelected ? 'text-highlight' : 'text-muted-foreground'
|
||||
} [overflow:hidden] [display:-webkit-box] [-webkit-line-clamp:2] [-webkit-box-orient:vertical]`}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="top"
|
||||
align="center"
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="top"
|
||||
align="center"
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span
|
||||
className={`text-base ${
|
||||
isSelected ? 'text-highlight' : 'text-muted-foreground'
|
||||
} [overflow:hidden] [display:-webkit-box] [-webkit-line-clamp:2] [-webkit-box-orient:vertical]`}
|
||||
>
|
||||
{title}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span
|
||||
className={`text-base ${
|
||||
isSelected ? 'text-highlight' : 'text-muted-foreground'
|
||||
} [overflow:hidden] [display:-webkit-box] [-webkit-line-clamp:2] [-webkit-box-orient:vertical]`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions and Visibility Toggle */}
|
||||
<div className="relative ml-2 flex items-center space-x-1">
|
||||
{/* Visibility Toggle Icon */}
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={`h-6 w-6 transition-opacity ${
|
||||
isSelected || !isVisible ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
|
||||
}`}
|
||||
aria-label={isVisible ? 'Hide' : 'Show'}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onToggleVisibility(e);
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isVisible ? <Icons.Hide className="h-6 w-6" /> : <Icons.Show className="h-6 w-6" />}
|
||||
</Button>
|
||||
|
||||
{/* Actions and Visibility Toggle */}
|
||||
<div className="relative ml-2 flex items-center space-x-1">
|
||||
{/* Visibility Toggle Icon */}
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={`h-6 w-6 transition-opacity ${
|
||||
isSelected || !isVisible ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
|
||||
}`}
|
||||
aria-label={isVisible ? 'Hide' : 'Show'}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onToggleVisibility(e);
|
||||
}}
|
||||
>
|
||||
{isVisible ? <Icons.Hide className="h-6 w-6" /> : <Icons.Show className="h-6 w-6" />}
|
||||
</Button>
|
||||
{/* Lock Icon (if needed) */}
|
||||
{isLocked && !disableEditing && (
|
||||
<Icons.Lock className="text-muted-foreground h-6 w-6" />
|
||||
)}
|
||||
|
||||
{/* Lock Icon (if needed) */}
|
||||
{isLocked && !disableEditing && <Icons.Lock className="text-muted-foreground h-6 w-6" />}
|
||||
|
||||
{/* Actions Dropdown Menu */}
|
||||
{disableEditing && <div className="h-6 w-6"></div>}
|
||||
{!disableEditing && (
|
||||
<DropdownMenu onOpenChange={open => setIsDropdownOpen(open)}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={`h-6 w-6 transition-opacity ${
|
||||
isSelected || isDropdownOpen
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 group-hover:opacity-100'
|
||||
}`}
|
||||
aria-label="Actions"
|
||||
dataCY="actionsMenuTrigger"
|
||||
onClick={e => e.stopPropagation()} // Prevent row selection on button click
|
||||
{/* Actions Dropdown Menu */}
|
||||
{disableEditing && <div className="h-6 w-6"></div>}
|
||||
{!disableEditing && (
|
||||
<DropdownMenu onOpenChange={open => setIsDropdownOpen(open)}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={`h-6 w-6 transition-opacity ${
|
||||
isSelected || isDropdownOpen
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 group-hover:opacity-100'
|
||||
}`}
|
||||
aria-label="Actions"
|
||||
dataCY="actionsMenuTrigger"
|
||||
onClick={e => e.stopPropagation()} // Prevent row selection on button click
|
||||
>
|
||||
<Icons.More className="h-6 w-6" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
// this was causing issue for auto focus on input dialog
|
||||
onCloseAutoFocus={e => e.preventDefault()}
|
||||
>
|
||||
<Icons.More className="h-6 w-6" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
// this was causing issue for auto focus on input dialog
|
||||
onCloseAutoFocus={e => e.preventDefault()}
|
||||
>
|
||||
<>
|
||||
<DropdownMenuItem onClick={e => handleAction('Rename', e)}>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span
|
||||
className="pl-2"
|
||||
data-cy="Rename"
|
||||
>
|
||||
Rename
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={e => handleAction('Delete', e)}>
|
||||
<Icons.Delete className="text-foreground" />
|
||||
<span
|
||||
className="pl-2"
|
||||
data-cy="Delete"
|
||||
>
|
||||
Delete
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
{onColor && (
|
||||
<DropdownMenuItem onClick={e => handleAction('Color', e)}>
|
||||
<Icons.ColorChange className="text-foreground" />
|
||||
<>
|
||||
<DropdownMenuItem onClick={e => handleAction('Rename', e)}>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span
|
||||
className="pl-2"
|
||||
data-cy="Change Color"
|
||||
data-cy="Rename"
|
||||
>
|
||||
Change Color
|
||||
Rename
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={e => handleAction('Lock', e)}>
|
||||
<Icons.Lock className="text-foreground" />
|
||||
<span
|
||||
className="pl-2"
|
||||
data-cy="LockToggle"
|
||||
>
|
||||
{isLocked ? 'Unlock' : 'Lock'}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Details Section */}
|
||||
{details && (details.primary?.length > 0 || details.secondary?.length > 0) && (
|
||||
<div className="ml-7 px-2 py-2">
|
||||
<div className="text-secondary-foreground flex items-center gap-1 text-base leading-normal">
|
||||
{details.primary?.length > 0 && renderDetails(details.primary)}
|
||||
{details.secondary?.length > 0 && (
|
||||
<div className="text-muted-foreground ml-auto text-sm">
|
||||
{renderDetails(details.secondary)}
|
||||
</div>
|
||||
<DropdownMenuItem onClick={e => handleAction('Delete', e)}>
|
||||
<Icons.Delete className="text-foreground" />
|
||||
<span
|
||||
className="pl-2"
|
||||
data-cy="Delete"
|
||||
>
|
||||
Delete
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
{onColor && (
|
||||
<DropdownMenuItem onClick={e => handleAction('Color', e)}>
|
||||
<Icons.ColorChange className="text-foreground" />
|
||||
<span
|
||||
className="pl-2"
|
||||
data-cy="Change Color"
|
||||
>
|
||||
Change Color
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem onClick={e => handleAction('Lock', e)}>
|
||||
<Icons.Lock className="text-foreground" />
|
||||
<span
|
||||
className="pl-2"
|
||||
data-cy="LockToggle"
|
||||
>
|
||||
{isLocked ? 'Unlock' : 'Lock'}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{/* Details Section */}
|
||||
{details && (details.primary?.length > 0 || details.secondary?.length > 0) && (
|
||||
<div className="ml-7 px-2 py-2">
|
||||
<div className="text-secondary-foreground flex items-center gap-1 text-base leading-normal">
|
||||
{details.primary?.length > 0 && renderDetails(details.primary)}
|
||||
{details.secondary?.length > 0 && (
|
||||
<div className="text-muted-foreground ml-auto text-sm">
|
||||
{renderDetails(details.secondary)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default DataRow;
|
||||
export { DataRow };
|
||||
|
||||
@ -112,10 +112,17 @@ interface ErrorBoundaryError extends Error {
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
enum ShowErrorDetails {
|
||||
always = 'always',
|
||||
dev = 'dev',
|
||||
production = 'production',
|
||||
}
|
||||
|
||||
interface DefaultFallbackProps extends FallbackProps {
|
||||
error: ErrorBoundaryError;
|
||||
context: string;
|
||||
resetErrorBoundary: () => void;
|
||||
showErrorDetails?: ShowErrorDetails;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
@ -126,13 +133,21 @@ interface ErrorBoundaryProps {
|
||||
children: React.ReactNode;
|
||||
fallbackRoute?: string | null;
|
||||
isPage?: boolean;
|
||||
showErrorDetails?: ShowErrorDetails;
|
||||
}
|
||||
|
||||
const DefaultFallback = ({
|
||||
error,
|
||||
context,
|
||||
resetErrorBoundary = () => {},
|
||||
showErrorDetails,
|
||||
}: DefaultFallbackProps) => {
|
||||
const isShowDetailsButtonVisible =
|
||||
showErrorDetails == null ||
|
||||
showErrorDetails === ShowErrorDetails.always ||
|
||||
(showErrorDetails === ShowErrorDetails.dev && !isProduction) ||
|
||||
(showErrorDetails === ShowErrorDetails.production && isProduction);
|
||||
|
||||
const { t } = useTranslation('ErrorBoundary');
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const { show } = useNotification();
|
||||
@ -165,17 +180,15 @@ const DefaultFallback = ({
|
||||
type: 'error',
|
||||
duration: 0,
|
||||
id: errorId,
|
||||
action: {
|
||||
label: t('Show Details'),
|
||||
onClick: () => setShowDetails(true),
|
||||
},
|
||||
action: isShowDetailsButtonVisible
|
||||
? {
|
||||
label: t('Show Details'),
|
||||
onClick: () => setShowDetails(true),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}, [error, errorTitle, subtitle, t, title, show]);
|
||||
|
||||
if (isProduction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={showDetails}
|
||||
@ -249,6 +262,7 @@ const ErrorBoundary = ({
|
||||
onError = _error => {},
|
||||
fallbackComponent: FallbackComponent = DefaultFallback,
|
||||
children,
|
||||
showErrorDetails,
|
||||
}: ErrorBoundaryProps) => {
|
||||
const [error, setError] = useState<ErrorBoundaryError | null>(null);
|
||||
|
||||
@ -303,6 +317,7 @@ const ErrorBoundary = ({
|
||||
<FallbackComponent
|
||||
{...props}
|
||||
context={context}
|
||||
showErrorDetails={showErrorDetails}
|
||||
/>
|
||||
)}
|
||||
onReset={onResetHandler}
|
||||
@ -315,6 +330,7 @@ const ErrorBoundary = ({
|
||||
error={error}
|
||||
context={context}
|
||||
resetErrorBoundary={() => setError(null)}
|
||||
showErrorDetails={showErrorDetails}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -54,7 +54,10 @@ const renderRangeSetting = option => {
|
||||
key={option.id}
|
||||
>
|
||||
<div className="w-1/3 text-[13px]">{option.name}</div>
|
||||
<div className="w-2/3">
|
||||
<div
|
||||
className="w-2/3"
|
||||
data-cy={option.id}
|
||||
>
|
||||
<RowInputRange
|
||||
minValue={option.min}
|
||||
maxValue={option.max}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { ScrollArea, DataRow } from '../../components';
|
||||
import { HoverCard, HoverCardTrigger, HoverCardContent } from '../../components/HoverCard';
|
||||
import { useSegmentationTableContext, useSegmentationExpanded } from './contexts';
|
||||
@ -23,6 +23,11 @@ export const SegmentationSegments = ({ children = null }: { children?: React.Rea
|
||||
let segmentation;
|
||||
let representation;
|
||||
|
||||
const activeSegmentRef = React.useRef<{
|
||||
element: HTMLElement | null;
|
||||
index: number | null;
|
||||
}>({ element: null, index: null });
|
||||
|
||||
try {
|
||||
// Try to use the SegmentationExpanded context if available
|
||||
const segmentationInfo = useSegmentationExpanded('SegmentationSegments');
|
||||
@ -38,108 +43,163 @@ export const SegmentationSegments = ({ children = null }: { children?: React.Rea
|
||||
}
|
||||
|
||||
const segments = Object.values(representation.segments);
|
||||
|
||||
// Find the active segment to scroll to it when it changes
|
||||
const activeSegment = segments.find(segment => {
|
||||
if (!segment) {
|
||||
return false;
|
||||
}
|
||||
const segmentFromSegmentation = segmentation.segments[segment.segmentIndex];
|
||||
return segmentFromSegmentation?.active;
|
||||
});
|
||||
|
||||
const isActiveSegmentation = segmentation.segmentationId === activeSegmentationId;
|
||||
|
||||
const { ref: scrollableContainerRef, maxHeight } = useDynamicMaxHeight(segments);
|
||||
|
||||
useEffect(() => {
|
||||
const activeSegmentIndex = activeSegmentRef.current.index;
|
||||
if (!activeSegmentIndex || activeSegmentIndex !== activeSegment?.segmentIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSegmentElement = activeSegmentRef.current.element;
|
||||
|
||||
if (!activeSegmentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the active segment is already visible.
|
||||
const activeSegmentElementBounds = activeSegmentElement.getBoundingClientRect();
|
||||
const scrollableContainerRect = scrollableContainerRef.current.getBoundingClientRect();
|
||||
if (
|
||||
activeSegmentElementBounds.top > scrollableContainerRect.top &&
|
||||
activeSegmentElementBounds.bottom < scrollableContainerRect.bottom
|
||||
) {
|
||||
// The active segment is already visible, so we don't need to scroll.
|
||||
return;
|
||||
}
|
||||
|
||||
activeSegmentElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}, [activeSegment?.segmentIndex, scrollableContainerRef]);
|
||||
|
||||
if (!representation || !segmentation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className={`bg-bkg-low space-y-px`}
|
||||
showArrows={true}
|
||||
>
|
||||
<div
|
||||
ref={scrollableContainerRef}
|
||||
style={{ maxHeight: maxHeight }}
|
||||
<div ref={scrollableContainerRef}>
|
||||
<ScrollArea
|
||||
className={`bg-bkg-low space-y-px`}
|
||||
showArrows={
|
||||
scrollableContainerRef?.current
|
||||
? scrollableContainerRef?.current?.offsetHeight >= parseFloat(maxHeight)
|
||||
: false
|
||||
}
|
||||
>
|
||||
{segments.map(segment => {
|
||||
if (!segment) {
|
||||
return null;
|
||||
}
|
||||
const { segmentIndex, color, visible } = segment as {
|
||||
segmentIndex: number;
|
||||
color: number[];
|
||||
visible: boolean;
|
||||
};
|
||||
const segmentFromSegmentation = segmentation.segments[segmentIndex];
|
||||
<div style={{ maxHeight: maxHeight }}>
|
||||
{segments.map(segment => {
|
||||
if (!segment) {
|
||||
return null;
|
||||
}
|
||||
const { segmentIndex, color, visible } = segment as {
|
||||
segmentIndex: number;
|
||||
color: number[];
|
||||
visible: boolean;
|
||||
};
|
||||
const segmentFromSegmentation = segmentation.segments[segmentIndex];
|
||||
|
||||
if (!segmentFromSegmentation) {
|
||||
return null;
|
||||
}
|
||||
if (!segmentFromSegmentation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { locked, active, label, displayText } = segmentFromSegmentation;
|
||||
const cssColor = `rgb(${color[0]},${color[1]},${color[2]})`;
|
||||
const { locked, active, label, displayText } = segmentFromSegmentation;
|
||||
const cssColor = `rgb(${color[0]},${color[1]},${color[2]})`;
|
||||
|
||||
const hasStats = segmentFromSegmentation.cachedStats?.namedStats;
|
||||
const DataRowComponent = (
|
||||
<DataRow
|
||||
key={segmentIndex}
|
||||
number={showSegmentIndex ? segmentIndex : null}
|
||||
title={label}
|
||||
// details={displayText}
|
||||
description={displayText}
|
||||
colorHex={cssColor}
|
||||
isSelected={active}
|
||||
isVisible={visible}
|
||||
isLocked={locked}
|
||||
disableEditing={disableEditing}
|
||||
className={!isActiveSegmentation ? 'opacity-80' : ''}
|
||||
onColor={() => onSegmentColorClick(segmentation.segmentationId, segmentIndex)}
|
||||
onToggleVisibility={() =>
|
||||
onToggleSegmentVisibility(
|
||||
segmentation.segmentationId,
|
||||
segmentIndex,
|
||||
representation.type
|
||||
)
|
||||
const hasStats = segmentFromSegmentation.cachedStats?.namedStats;
|
||||
|
||||
const segmentRowRef = (element: HTMLElement) => {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
onToggleLocked={() => onToggleSegmentLock(segmentation.segmentationId, segmentIndex)}
|
||||
onSelect={() => onSegmentClick(segmentation.segmentationId, segmentIndex)}
|
||||
onRename={() => onSegmentEdit(segmentation.segmentationId, segmentIndex)}
|
||||
onDelete={() => onSegmentDelete(segmentation.segmentationId, segmentIndex)}
|
||||
/>
|
||||
);
|
||||
|
||||
return hasStats ? (
|
||||
<HoverCard
|
||||
key={`hover-${segmentIndex}`}
|
||||
openDelay={300}
|
||||
>
|
||||
<HoverCardTrigger asChild>
|
||||
<div>{DataRowComponent}</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="w-72 border"
|
||||
>
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<div
|
||||
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: cssColor }}
|
||||
></div>
|
||||
<h3 className="text-muted-foreground break-words font-semibold">{label}</h3>
|
||||
</div>
|
||||
if (element) {
|
||||
activeSegmentRef.current = { element, index: segmentIndex };
|
||||
} else {
|
||||
activeSegmentRef.current = { element: null, index: null };
|
||||
}
|
||||
};
|
||||
|
||||
<SegmentStatistics
|
||||
segment={{
|
||||
...segmentFromSegmentation,
|
||||
const DataRowComponent = (
|
||||
<DataRow
|
||||
ref={segmentRowRef}
|
||||
key={segmentIndex}
|
||||
number={showSegmentIndex ? segmentIndex : null}
|
||||
title={label}
|
||||
// details={displayText}
|
||||
description={displayText}
|
||||
colorHex={cssColor}
|
||||
isSelected={active}
|
||||
isVisible={visible}
|
||||
isLocked={locked}
|
||||
disableEditing={disableEditing}
|
||||
className={!isActiveSegmentation ? 'opacity-80' : ''}
|
||||
onColor={() => onSegmentColorClick(segmentation.segmentationId, segmentIndex)}
|
||||
onToggleVisibility={() =>
|
||||
onToggleSegmentVisibility(
|
||||
segmentation.segmentationId,
|
||||
segmentIndex,
|
||||
}}
|
||||
segmentationId={segmentation.segmentationId}
|
||||
representation.type
|
||||
)
|
||||
}
|
||||
onToggleLocked={() =>
|
||||
onToggleSegmentLock(segmentation.segmentationId, segmentIndex)
|
||||
}
|
||||
onSelect={() => onSegmentClick(segmentation.segmentationId, segmentIndex)}
|
||||
onRename={() => onSegmentEdit(segmentation.segmentationId, segmentIndex)}
|
||||
onDelete={() => onSegmentDelete(segmentation.segmentationId, segmentIndex)}
|
||||
/>
|
||||
);
|
||||
|
||||
return hasStats ? (
|
||||
<HoverCard
|
||||
key={`hover-${segmentIndex}`}
|
||||
openDelay={300}
|
||||
>
|
||||
<HoverCardTrigger asChild>
|
||||
<div>{DataRowComponent}</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="w-72 border"
|
||||
>
|
||||
{children}
|
||||
</SegmentStatistics>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
) : (
|
||||
DataRowComponent
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<div
|
||||
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: cssColor }}
|
||||
></div>
|
||||
<h3 className="text-muted-foreground break-words font-semibold">{label}</h3>
|
||||
</div>
|
||||
|
||||
<SegmentStatistics
|
||||
segment={{
|
||||
...segmentFromSegmentation,
|
||||
segmentIndex,
|
||||
}}
|
||||
segmentationId={segmentation.segmentationId}
|
||||
>
|
||||
{children}
|
||||
</SegmentStatistics>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
) : (
|
||||
DataRowComponent
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,29 @@
|
||||
import { useRef, useState, useEffect, RefObject } from 'react';
|
||||
|
||||
const _getMovementIntersectionObserver = ({
|
||||
callback,
|
||||
rootMargin,
|
||||
threshold,
|
||||
}: {
|
||||
callback: () => void;
|
||||
rootMargin: string;
|
||||
threshold: number[];
|
||||
}): IntersectionObserver => {
|
||||
return new IntersectionObserver(
|
||||
entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
threshold,
|
||||
rootMargin,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the maximum height for an element based on its position
|
||||
* relative to the bottom of the viewport.
|
||||
@ -31,20 +55,49 @@ export function useDynamicMaxHeight(
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate initially
|
||||
// Use requestAnimationFrame to ensure layout is stable after initial render
|
||||
const rafId = requestAnimationFrame(calculateMaxHeight);
|
||||
// Two intersection observers to trigger a recalculation when the target element
|
||||
// moves up or down. One for moving up and one for moving down.
|
||||
// Note that with this approach we don't need to use a resize observer nor
|
||||
// a window resize listener.
|
||||
|
||||
// Recalculate on window resize
|
||||
window.addEventListener('resize', calculateMaxHeight);
|
||||
// The trick is to use a margin for the IntersectionObserver to detect movement.
|
||||
// See more below.
|
||||
const rootMarginHeight = maxHeight === '100vh' ? `${window.innerHeight}px` : `${maxHeight}`;
|
||||
|
||||
// Note that we use a fine grained threshold because we don't know how
|
||||
// much it will move and we want any movement to trigger the intersection observer.
|
||||
const threshold = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
|
||||
|
||||
// The trick here is to use the calculated maxHeight as the root margin height
|
||||
// so that any movement of the target element down (i.e. "out of the" viewport)
|
||||
// will trigger the intersection observer.
|
||||
const moveDownIntersectionObserver = _getMovementIntersectionObserver({
|
||||
callback: calculateMaxHeight,
|
||||
rootMargin: `0px 0px ${rootMarginHeight} 0px`,
|
||||
threshold,
|
||||
});
|
||||
|
||||
// The trick here is to use the calculated maxHeight as the negative
|
||||
// root margin height so that any movement of the target element up
|
||||
// (i.e. "into the" viewport) will trigger the intersection observer.
|
||||
const moveUpIntersectionObserver = _getMovementIntersectionObserver({
|
||||
callback: calculateMaxHeight,
|
||||
rootMargin: `0px 0px -${rootMarginHeight} 0px`,
|
||||
threshold,
|
||||
});
|
||||
|
||||
if (ref.current) {
|
||||
moveUpIntersectionObserver.observe(ref.current);
|
||||
moveDownIntersectionObserver.observe(ref.current);
|
||||
}
|
||||
|
||||
// Cleanup listener and requestAnimationFrame on component unmount
|
||||
return () => {
|
||||
window.removeEventListener('resize', calculateMaxHeight);
|
||||
cancelAnimationFrame(rafId);
|
||||
moveUpIntersectionObserver.disconnect();
|
||||
moveDownIntersectionObserver.disconnect();
|
||||
};
|
||||
// Dependencies: buffer, minHeight, and data.
|
||||
}, [data, buffer, minHeight]);
|
||||
}, [data, buffer, minHeight, maxHeight]);
|
||||
|
||||
return { ref, maxHeight };
|
||||
}
|
||||
|
||||
@ -19,23 +19,20 @@
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rm -rf node_modules/.cache/storybook && shx rm -rf dist",
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"start": "yarn run build --watch",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"dev": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
"build:package": "yarn run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/react": "^13.1.0",
|
||||
"browser-detect": "^0.2.28",
|
||||
"classnames": "^2.3.2",
|
||||
"@testing-library/react": "13.4.0",
|
||||
"browser-detect": "0.2.28",
|
||||
"classnames": "2.5.1",
|
||||
"d3-array": "3",
|
||||
"d3-axis": "3",
|
||||
"d3-scale": "4",
|
||||
@ -43,45 +40,36 @@
|
||||
"d3-selection": "3",
|
||||
"d3-shape": "3",
|
||||
"d3-zoom": "3",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.merge": "^4.6.1",
|
||||
"moment": "^2.30.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dates": "^21.8.0",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.merge": "4.6.2",
|
||||
"moment": "2.30.1",
|
||||
"mousetrap": "1.6.5",
|
||||
"react": "18.3.1",
|
||||
"react-dates": "21.8.0",
|
||||
"react-dnd": "14.0.2",
|
||||
"react-dnd-html5-backend": "14.0.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-dom": "18.3.1",
|
||||
"react-draggable": "4.5.0",
|
||||
"react-error-boundary": "3.1.4",
|
||||
"react-modal": "3.11.2",
|
||||
"react-outside-click-handler": "^1.3.0",
|
||||
"react-outside-click-handler": "1.3.0",
|
||||
"react-select": "5.7.4",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"react-window": "^1.8.9",
|
||||
"react-with-direction": "^1.3.1",
|
||||
"swiper": "^8.4.2",
|
||||
"react-test-renderer": "18.3.1",
|
||||
"react-window": "1.8.11",
|
||||
"react-with-direction": "1.4.0",
|
||||
"swiper": "8.4.7",
|
||||
"webpack": "5.95.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.28.0",
|
||||
"@storybook/addon-actions": "^7.6.10",
|
||||
"@storybook/addon-docs": "^7.6.10",
|
||||
"@storybook/addon-essentials": "^7.6.10",
|
||||
"@storybook/addon-links": "^7.6.10",
|
||||
"@storybook/cli": "^7.6.10",
|
||||
"@storybook/react": "^7.6.10",
|
||||
"@storybook/react-webpack5": "^7.6.10",
|
||||
"@storybook/source-loader": "^7.6.10",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"babel-loader": "^9.1.2",
|
||||
"dotenv-webpack": "^8.0.1",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-loader": "^7.2.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"storybook": "^7.6.10",
|
||||
"autoprefixer": "10.4.21",
|
||||
"babel-loader": "8.4.1",
|
||||
"dotenv-webpack": "1.8.0",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-loader": "6.2.1",
|
||||
"prop-types": "15.8.1",
|
||||
"remark-gfm": "3.0.1",
|
||||
"tailwindcss": "3.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
144
tests/ContourSegLocking.spec.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { test, expect } from 'playwright-test-coverage';
|
||||
import { visitStudy } from './utils';
|
||||
import { viewportSVGPathLocator } from './utils/locators';
|
||||
import { simulateNormalizedDragOnElement } from './utils/simulateDragOnElement';
|
||||
|
||||
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
|
||||
|
||||
test('should not allow contours to be edited in basic viewer mode', async ({ page }) => {
|
||||
const mode = 'viewer';
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
|
||||
await page.getByTestId('side-panel-header-right').click();
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
// Wait for the segmentation to be loaded.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
|
||||
// Wait for the segmentation to hydrate.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
const svgPathLocatorPreEdit = viewportSVGPathLocator({ page, viewportId: 'default' });
|
||||
|
||||
expect(
|
||||
await svgPathLocatorPreEdit.count(),
|
||||
'Expected exactly 1 path element in the viewport'
|
||||
).toBe(1);
|
||||
|
||||
const expectedPathCommands = await svgPathLocatorPreEdit.getAttribute('d');
|
||||
|
||||
// Try to drag one of the edges of the rectangular contour.
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: svgPathLocatorPreEdit,
|
||||
start: { x: 0.1, y: 0 },
|
||||
end: { x: 0.1, y: -0.2 },
|
||||
});
|
||||
|
||||
const svgPathLocatorPostEdit = viewportSVGPathLocator({ page, viewportId: 'default' });
|
||||
|
||||
expect(
|
||||
await svgPathLocatorPostEdit.getAttribute('d'),
|
||||
'Expected the path commands to be the same as the pre-edit path commands'
|
||||
).toBe(expectedPathCommands);
|
||||
});
|
||||
|
||||
test('should not allow contours to be edited when panelSegmentation.disableEditing is true', async ({
|
||||
page,
|
||||
}) => {
|
||||
const mode = 'segmentation';
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
|
||||
await page.getByTestId('side-panel-header-right').click();
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
// Wait for the segmentation to be loaded.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// disable editing of segmentations via the customization service
|
||||
await page.evaluate(() => {
|
||||
window.services.customizationService.setGlobalCustomization(
|
||||
'panelSegmentation.disableEditing',
|
||||
{
|
||||
$set: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
|
||||
// Wait for the segmentation to hydrate.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
const svgPathLocatorPreEdit = viewportSVGPathLocator({ page, viewportId: 'default' });
|
||||
|
||||
expect(
|
||||
await svgPathLocatorPreEdit.count(),
|
||||
'Expected exactly 1 path element in the viewport'
|
||||
).toBe(1);
|
||||
|
||||
const expectedPathCommands = await svgPathLocatorPreEdit.getAttribute('d');
|
||||
|
||||
// Try to drag one of the edges of the rectangular contour.
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: svgPathLocatorPreEdit,
|
||||
start: { x: 0.1, y: 0 },
|
||||
end: { x: 0.1, y: -0.2 },
|
||||
});
|
||||
|
||||
const svgPathLocatorPostEdit = viewportSVGPathLocator({ page, viewportId: 'default' });
|
||||
|
||||
expect(
|
||||
await svgPathLocatorPostEdit.getAttribute('d'),
|
||||
'Expected the path commands to be the same as the pre-edit path commands'
|
||||
).toBe(expectedPathCommands);
|
||||
});
|
||||
|
||||
test('should allow contours to be edited when panelSegmentation.disableEditing is false', async ({
|
||||
page,
|
||||
}) => {
|
||||
const mode = 'segmentation';
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
|
||||
await page.getByTestId('side-panel-header-right').click();
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
// Wait for the segmentation to be loaded.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// disable editing of segmentations via the customization service
|
||||
await page.evaluate(() => {
|
||||
window.services.customizationService.setGlobalCustomization(
|
||||
'panelSegmentation.disableEditing',
|
||||
{
|
||||
$set: false,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
|
||||
// Wait for the segmentation to hydrate.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
const svgPathLocatorPreEdit = viewportSVGPathLocator({ page, viewportId: 'default' });
|
||||
|
||||
expect(
|
||||
await svgPathLocatorPreEdit.count(),
|
||||
'Expected exactly 1 path element in the viewport'
|
||||
).toBe(1);
|
||||
|
||||
const preEditPathCommands = await svgPathLocatorPreEdit.getAttribute('d');
|
||||
|
||||
// Try to drag one of the edges of the rectangular contour.
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: svgPathLocatorPreEdit,
|
||||
start: { x: 0.1, y: 0 },
|
||||
end: { x: 0.1, y: -0.2 },
|
||||
});
|
||||
|
||||
const svgPathLocatorPostEdit = viewportSVGPathLocator({ page, viewportId: 'default' });
|
||||
|
||||
expect(
|
||||
await svgPathLocatorPostEdit.getAttribute('d'),
|
||||
'Not expecting the path commands to be the same as the pre-edit path commands'
|
||||
).not.toBe(preEditPathCommands);
|
||||
});
|
||||
118
tests/LabelMapSegLocking.spec.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { test } from 'playwright-test-coverage';
|
||||
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
|
||||
import { press } from './utils/keyboardUtils';
|
||||
import { simulateNormalizedDragOnElement } from './utils/simulateDragOnElement';
|
||||
import { viewportLocator } from './utils/locators';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
const studyInstanceUID = '1.3.6.1.4.1.14519.5.2.1.256467663913010332776401703474716742458';
|
||||
const mode = 'segmentation';
|
||||
await visitStudy(page, studyInstanceUID, mode, 2000);
|
||||
});
|
||||
|
||||
test('should prevent editing of label map segmentations when panelSegmentation.disableEditing is true', async ({
|
||||
page,
|
||||
}) => {
|
||||
// disable editing of segmentations via the customization service
|
||||
await page.evaluate(() => {
|
||||
window.services.customizationService.setGlobalCustomization(
|
||||
'panelSegmentation.disableEditing',
|
||||
{
|
||||
$set: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
// Wait for the segmentation to be loaded.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
|
||||
// Wait for the segmentation to hydrate.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// navigate to the 12th image and ensure the correct overlay is displayed
|
||||
await press({ page, key: 'ArrowDown', nTimes: 11 });
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalLockedSegPreEdit);
|
||||
|
||||
// Attempt to erase the segmentations.
|
||||
await page.getByTestId('Eraser-btn').click();
|
||||
|
||||
// Use the largest eraser radius to help ensure the entire image is erased.
|
||||
await page.locator(`css=div[data-cy="eraser-radius"] input`).fill('1000');
|
||||
|
||||
// Attempt to erase the segmentations by dragging the eraser tool across the image several times.
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: viewportLocator({ page, viewportId: 'default' }),
|
||||
start: { x: 0.01, y: 0.25 },
|
||||
end: { x: 1.0, y: 0.25 },
|
||||
});
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: viewportLocator({ page, viewportId: 'default' }),
|
||||
start: { x: 0.01, y: 0.5 },
|
||||
end: { x: 1.0, y: 0.5 },
|
||||
});
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: viewportLocator({ page, viewportId: 'default' }),
|
||||
start: { x: 0.01, y: 0.75 },
|
||||
end: { x: 1.0, y: 0.75 },
|
||||
});
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalLockedSegPostEdit);
|
||||
});
|
||||
|
||||
test('should allow editing of label map segmentations when panelSegmentation.disableEditing is false', async ({
|
||||
page,
|
||||
}) => {
|
||||
// disable editing of segmentations via the customization service
|
||||
await page.evaluate(() => {
|
||||
window.services.customizationService.setGlobalCustomization(
|
||||
'panelSegmentation.disableEditing',
|
||||
{
|
||||
$set: false,
|
||||
}
|
||||
);
|
||||
});
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
// Wait for the segmentation to be loaded.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
// Wait for the segmentation to hydrate.
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// navigate to the 12th image and ensure the correct overlay is displayed
|
||||
await press({ page, key: 'ArrowDown', nTimes: 11 });
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.labelMapSegLocking.globalUnlockedSegPreEdit);
|
||||
|
||||
// Attempt to erase the segmentations.
|
||||
await page.getByTestId('Eraser-btn').click();
|
||||
|
||||
// Use the largest eraser radius to help ensure the eraser passes over the entire image.
|
||||
await page.locator(`css=div[data-cy="eraser-radius"] input`).fill('1000');
|
||||
|
||||
// Attempt to erase the segmentations by dragging the eraser tool across the image several times.
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: viewportLocator({ page, viewportId: 'default' }),
|
||||
start: { x: 0.01, y: 0.25 },
|
||||
end: { x: 1.0, y: 0.25 },
|
||||
});
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: viewportLocator({ page, viewportId: 'default' }),
|
||||
start: { x: 0.01, y: 0.5 },
|
||||
end: { x: 1.0, y: 0.5 },
|
||||
});
|
||||
await simulateNormalizedDragOnElement({
|
||||
locator: viewportLocator({ page, viewportId: 'default' }),
|
||||
start: { x: 0.01, y: 0.75 },
|
||||
end: { x: 1.0, y: 0.75 },
|
||||
});
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
page,
|
||||
screenShotPaths.labelMapSegLocking.globalUnlockedSegPostEdit
|
||||
);
|
||||
});
|
||||
@ -16,7 +16,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
|
||||
await page.getByTestId('MPR').click();
|
||||
|
||||
// Wait 5 seconds for MPR to load. This is necessary in particular when screen shots are added or replaced.
|
||||
await page.waitForTimeout(5000);
|
||||
await page.waitForTimeout(10000);
|
||||
|
||||
await checkForScreenshot(
|
||||
page,
|
||||
|
||||
@ -10,9 +10,14 @@ test.beforeEach(async ({ page }) => {
|
||||
test('should hydrate RT reports correctly', async ({ page }) => {
|
||||
await page.getByTestId('side-panel-header-right').click();
|
||||
await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPreHydration);
|
||||
|
||||
await page.getByTestId('yes-hydrate-btn').click();
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtPostHydration);
|
||||
|
||||
await page.getByText('Small Sphere').click();
|
||||
await page.waitForTimeout(5000);
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydration.rtJumpToStructure);
|
||||
});
|
||||
|
||||
@ -13,7 +13,7 @@ test('should hydrate an RTSTRUCT from MPR', async ({ page }) => {
|
||||
await page.getByTestId('Layout').click();
|
||||
await page.getByTestId('MPR').click();
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
await page.waitForTimeout(10000);
|
||||
|
||||
await checkForScreenshot(page, page, screenShotPaths.rtHydrationFromMPR.mprBeforeRT);
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { test, expect } from 'playwright-test-coverage';
|
||||
import { visitStudy, simulateNormalizedClickOnElement } from './utils/index';
|
||||
import { viewportLocator } from './utils/locators';
|
||||
import { downloadAsString } from './utils/download';
|
||||
import toArray from 'extensions/dicom-microscopy/src/utils/toArray';
|
||||
|
||||
test('should create and download the TMTV CSV report correctly', async ({ page }) => {
|
||||
const studyInstanceUID = '1.2.840.113619.2.290.3.3767434740.226.1600859119.501';
|
||||
@ -26,14 +27,22 @@ test('should create and download the TMTV CSV report correctly', async ({ page }
|
||||
'202009231_tmtv.csv'
|
||||
);
|
||||
|
||||
const tmtvCSVReportContent = await downloadAsString(download);
|
||||
const tmtvCSVReportContent: string = await downloadAsString(download);
|
||||
|
||||
const expectedHeaders =
|
||||
'PatientID,PatientName,SeriesInstanceUID,StudyDate,StudyInstanceUID,center,count,id,kurtosis,label,lesionGlycolysis,max,maxLPS,mean,median,min,minLPS,peakLPS,peakValue,skewness,stdDev,volume';
|
||||
|
||||
const tmtvCSVReportHeaders = tmtvCSVReportContent
|
||||
.substring(0, expectedHeaders.length)
|
||||
.split(',')
|
||||
.sort()
|
||||
.join(',');
|
||||
|
||||
expect(
|
||||
tmtvCSVReportContent,
|
||||
tmtvCSVReportHeaders,
|
||||
'Expected the file to start with specific column/value headers'
|
||||
).toMatch(
|
||||
/^id,label,min,max,mean,stdDev,median,skewness,kurtosis,count,maxLPS,minLPS,center,volume,peakValue,peakLPS,lesionGlycolysis,PatientID,PatientName,StudyInstanceUID,SeriesInstanceUID,StudyDate/
|
||||
);
|
||||
).toBe(expectedHeaders);
|
||||
|
||||
expect(tmtvCSVReportContent, 'Expected the patient name to be present').toContain(
|
||||
'Water Phantom'
|
||||
);
|
||||
|
||||
|
Before Width: | Height: | Size: 324 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 251 KiB After Width: | Height: | Size: 271 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 268 KiB |
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 341 KiB |
|
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 208 KiB After Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 299 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 262 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 166 KiB |