diff --git a/examples/vite-redux-toolkit-react-app/package.json b/examples/vite-redux-toolkit-react-app/package.json index a5c619103..9147d9f8b 100644 --- a/examples/vite-redux-toolkit-react-app/package.json +++ b/examples/vite-redux-toolkit-react-app/package.json @@ -25,26 +25,24 @@ "@metacell/geppetto-meta-ui": "^2.0.0", "@mui/icons-material": "^6.1.1", "@mui/material": "^6.1.1", - "@react-three/drei": "^9.114.2", - "@react-three/fiber": "^8.17.9", "@reduxjs/toolkit": "^2.2.7", "@types/react-redux": "^7.1.34", "ami.js": ">=0.32.0", "file-saver": "^2.0.5", "jszip": "^3.10.1", + "nifti-reader-js": "^0.6.8", "openseadragon": "^5.0.0", "react": "^18.3.1", "react-color": "^2.19.3", "react-dom": "^18.3.1", "react-redux": "^9.1.2", - "three": "^0.169.0", - "three-render-objects": "^1.29.5" + "three": "^0.118.0", + "three-render-objects": ">=1.13.3" }, "devDependencies": { "@eslint/js": "^9.9.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@types/three": "^0.169.0", "@vitejs/plugin-react": "^4.3.1", "eslint": "^9.9.0", "eslint-plugin-json": "^4.0.1", diff --git a/examples/vite-redux-toolkit-react-app/public/assets/EX_SITU_2009_UCSD_T1_WEIGHTED.nii.gz b/examples/vite-redux-toolkit-react-app/public/assets/EX_SITU_2009_UCSD_T1_WEIGHTED.nii.gz new file mode 100644 index 000000000..c0a4fc17a Binary files /dev/null and b/examples/vite-redux-toolkit-react-app/public/assets/EX_SITU_2009_UCSD_T1_WEIGHTED.nii.gz differ diff --git a/examples/vite-redux-toolkit-react-app/src/components/LeftSidebar.tsx b/examples/vite-redux-toolkit-react-app/src/components/LeftSidebar.tsx index bd8750283..561a8f7a9 100644 --- a/examples/vite-redux-toolkit-react-app/src/components/LeftSidebar.tsx +++ b/examples/vite-redux-toolkit-react-app/src/components/LeftSidebar.tsx @@ -8,8 +8,7 @@ import Drawer from "@mui/material/Drawer"; import { Theme } from "@mui/material/styles"; import {addWidget, deleteWidget} from "@metacell/geppetto-meta-client/common/layout/actions"; import { - componentWidget, - threeDViewerWidget, + componentWidget, DicomViewerWidget, } from "../layoutManager/widgets.ts"; import { useDispatch } from "react-redux"; import { FormControlLabel, FormGroup } from "@mui/material"; @@ -23,7 +22,7 @@ const drawerWidth = 240; const viewers = { [ViewerType.default]: componentWidget(), - [ViewerType.ThreeD]: threeDViewerWidget(), + [ViewerType.dicomViewer]: DicomViewerWidget(), }; interface LeftSidebarProps { diff --git a/examples/vite-redux-toolkit-react-app/src/components/viewers/Dicom/DicomViewer.tsx b/examples/vite-redux-toolkit-react-app/src/components/viewers/Dicom/DicomViewer.tsx new file mode 100644 index 000000000..4969a1377 --- /dev/null +++ b/examples/vite-redux-toolkit-react-app/src/components/viewers/Dicom/DicomViewer.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; +import DicomViewer from '@metacell/geppetto-meta-ui/dicom-viewer/preconf/DicomViewer'; +import Loader from "@metacell/geppetto-meta-ui/loader/Loader"; + +const DicomViewerExample: React.FC = () => { + const [ready, setReady] = useState(false); + + const onLoaded = () => { + setReady(true); + }; + + const data = '/assets/EX_SITU_2009_UCSD_T1_WEIGHTED.nii.gz'; + + return <> + + + +}; + +export default DicomViewerExample; diff --git a/examples/vite-redux-toolkit-react-app/src/layoutManager/componentsMap.tsx b/examples/vite-redux-toolkit-react-app/src/layoutManager/componentsMap.tsx index c86ce2c34..6576feb45 100644 --- a/examples/vite-redux-toolkit-react-app/src/layoutManager/componentsMap.tsx +++ b/examples/vite-redux-toolkit-react-app/src/layoutManager/componentsMap.tsx @@ -1,9 +1,10 @@ import MyComponent from '../components/MyComponent' -import ThreeDViewer from "../components/viewers/ThreeD/ThreeDViewer.tsx"; +import DicomViewer from "../components/viewers/Dicom/DicomViewer.tsx"; +import DicomViewerExample from '@metacell/geppetto-meta-ui/dicom-viewer/DicomViewer'; const componentMap = { - MyComponent, - ThreeDViewer, + 'MyComponent': MyComponent, + 'Dicom Viewer': DicomViewer } export default componentMap diff --git a/examples/vite-redux-toolkit-react-app/src/layoutManager/widgets.ts b/examples/vite-redux-toolkit-react-app/src/layoutManager/widgets.ts index cc8243b56..6d9d8de6a 100644 --- a/examples/vite-redux-toolkit-react-app/src/layoutManager/widgets.ts +++ b/examples/vite-redux-toolkit-react-app/src/layoutManager/widgets.ts @@ -5,14 +5,14 @@ export const componentWidget = () => ({ name: 'panel1', component: "MyComponent", panelName: 'leftPanel', - enableClose: true, + enableClose: false, status: WidgetStatus.ACTIVE }); -export const threeDViewerWidget = () => ({ - id: '3D', - name: "3D Viewer", - component: 'ThreeDViewer', +export const DicomViewerWidget = () => ({ + id: 'Dicom Viewer', + name: "DicomViewer Viewer", + component: 'Dicom Viewer', panelName: "leftPanel", enableClose: false, status: WidgetStatus.ACTIVE diff --git a/examples/vite-redux-toolkit-react-app/src/main.tsx b/examples/vite-redux-toolkit-react-app/src/main.tsx index 9e669a6a0..1d66fbb7b 100644 --- a/examples/vite-redux-toolkit-react-app/src/main.tsx +++ b/examples/vite-redux-toolkit-react-app/src/main.tsx @@ -6,9 +6,9 @@ import store from './redux/store' import {Provider} from "react-redux"; createRoot(document.getElementById('root')!).render( - + // - , + // , ) diff --git a/examples/vite-redux-toolkit-react-app/src/models/models.ts b/examples/vite-redux-toolkit-react-app/src/models/models.ts index d338e2c1e..ddd7eeb24 100644 --- a/examples/vite-redux-toolkit-react-app/src/models/models.ts +++ b/examples/vite-redux-toolkit-react-app/src/models/models.ts @@ -1,6 +1,5 @@ export enum ViewerType { default = "Default", ThreeD = "3D", - imageViewer= 'Image Viewer', dicomViewer= 'Dicom Viewer', } \ No newline at end of file diff --git a/examples/vite-redux-toolkit-react-app/src/redux/store.ts b/examples/vite-redux-toolkit-react-app/src/redux/store.ts index 71f1699aa..b90dbc150 100644 --- a/examples/vite-redux-toolkit-react-app/src/redux/store.ts +++ b/examples/vite-redux-toolkit-react-app/src/redux/store.ts @@ -41,7 +41,6 @@ const getLayoutManagerAndStore = () => { preloadedState: Partial; reducer: (state: RootState | undefined, action: Action) => RootState; // eslint-disable-next-line @typescript-eslint/no-explicit-any - middleware: (getDefaultMiddleware: ReturnType>) => any; } = { reducer: rootReducer, middleware: middlewareEnhancer, diff --git a/examples/vite-redux-toolkit-react-app/vite.config.ts b/examples/vite-redux-toolkit-react-app/vite.config.ts index eece997e0..aa7c9511f 100644 --- a/examples/vite-redux-toolkit-react-app/vite.config.ts +++ b/examples/vite-redux-toolkit-react-app/vite.config.ts @@ -1,10 +1,30 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +const gzipFixPlugin = () => { + const fixHeader = (server) => { + server.middlewares.use((req, res, next) => { + if (req.originalUrl?.includes(".gz")) { + res.setHeader("Content-Type", "gzip"); + res.setHeader("Content-Encoding", "invalid-data"); + } + next(); + }); + }; + + return { + name: "gzip-fix-plugin", + configureServer: fixHeader, + // vite dev and vite preview use different server, so we need to configure both. + configurePreviewServer: fixHeader, + }; +}; + // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), gzipFixPlugin()], optimizeDeps: { exclude: ['@metacell/geppetto-meta-core', '@metacell/geppetto-meta-client', '@metacell/geppetto-meta-ui'] }, + assetsInclude: ['**/*.nii.gz'], }) diff --git a/geppetto-showcase/src/examples/dicom-viewer/DicomViewerExample.js b/geppetto-showcase/src/examples/dicom-viewer/DicomViewerExample.js index 72777ff0a..6d83abad2 100644 --- a/geppetto-showcase/src/examples/dicom-viewer/DicomViewerExample.js +++ b/geppetto-showcase/src/examples/dicom-viewer/DicomViewerExample.js @@ -1,21 +1,67 @@ import React, { Component } from 'react'; import DicomViewer from '@metacell/geppetto-meta-ui/dicom-viewer/DicomViewer'; import Loader from "@metacell/geppetto-meta-ui/loader/Loader"; +import { + faThLarge, + faSquare, + faExchangeAlt, + faExpandAlt, + faCompressAlt, +} from '@fortawesome/free-solid-svg-icons'; export default class DicomViewerExample extends Component { constructor (props) { super(props); - this.state = { ready: true }; + this.state = { ready: true, fullscreen: false, mode: 'quad_view', orientation: '3d' }; this.onLoaded = this.onLoaded.bind(this) - + this.changeMode = this.changeMode.bind(this) + this.changeOrientation = this.changeOrientation.bind(this) + this.restore = this.restore.bind(this) + this.fullScreen = this.fullScreen.bind(this) } componentDidMount () { - this.setState({ ready: false }); + this.setState({ ...this.state, ready: false }); } onLoaded (){ - this.setState({ ready: true }); + this.setState({ ...this.state, ready: true }); + } + + changeMode () { + if (this.state.mode === 'single_view') { + this.setState({ ...this.state, mode: 'quad_view' }); + } else { + this.setState({ ...this.state, mode: 'single_view' }); + } + } + + changeOrientation () { + let newOrientation; + switch (this.state.orientation) { + case 'coronal': + newOrientation = 'sagittal'; + break; + case 'sagittal': + newOrientation = 'axial'; + break; + case 'axial': + newOrientation = '3d'; + break; + case '3d': + default: + newOrientation = 'coronal'; + break; + } + this.setState({ ...this.state, orientation: newOrientation }); + } + + restore () { + this.setState({ ...this.state, fullScreen: false }); + } + + fullScreen () { + this.setState({ ...this.state, fullScreen: true }); } render () { @@ -36,15 +82,33 @@ export default class DicomViewerExample extends Component { > console.log("Right click!", event)} showDownloadButton={true} onLoaded={this.onLoaded} toolbarOptions={{ innerDivStyles: { backgroundColor: 'rgb(0,0,0,0);' } }} + toolbarButtons={{ + single_view: [ + { icon: faThLarge, tooltip: 'Multi View', action: this.changeMode }, + { icon: faExchangeAlt, tooltip: 'Change Orientation', action: this.changeOrientation } + ], + quad_view: [ + { icon: faSquare, tooltip: 'Single View', action: this.changeMode } + ], + fullScreen: [ + { icon: faCompressAlt, tooltip: 'Restore', action: this.restore } + ], + minimized: [ + { icon: faExpandAlt, tooltip: 'Maximize', action: this.fullScreen } + ] + }} /> ) : } -} +} \ No newline at end of file diff --git a/geppetto-showcase/src/pages/index.js b/geppetto-showcase/src/pages/index.js index d70f44524..589f9d623 100644 --- a/geppetto-showcase/src/pages/index.js +++ b/geppetto-showcase/src/pages/index.js @@ -4,6 +4,7 @@ const BigImgViewer = lazy(() => import(/* webpackChunkName: "bigimageviewer" */' const Canvas = lazy(() => import(/* webpackChunkName: "canvas" */'./dataviewers/canvas')); const ConnectivityViewer = lazy(() => import(/* webpackChunkName: "connectivityviewer" */'./dataviewers/connectivityviewer')); const DicomViewer = lazy(() => import(/* webpackChunkName: "dicomviewer" */'./dataviewers/dicomviewer')); +const DicomViewerPreconf = lazy(() => import(/* webpackChunkName: "dicomviewerPreconf" */'./dataviewers/dicomviewerPreconf')); const GraphVisualizer = lazy(() => import(/* webpackChunkName: "graphvisualizer" */'./dataviewers/graphvisualizer')); const HTMLViewer = lazy(() => import(/* webpackChunkName: "htmlviewer" */'./dataviewers/htmlviewer')); const MoviePlayer = lazy(() => import(/* webpackChunkName: "movieplayer" */'./dataviewers/movieplayer')); @@ -43,6 +44,12 @@ const pages = [ name: 'Dicom Viewer', to: '/dataviewers/dicomviewer', }, + { + component: DicomViewerPreconf, + parent: 'Data Viewers', + name: 'Preconfigured Dicom Viewer', + to: '/dataviewers/dicomviewerPreconf', + }, { component: GraphVisualizer, parent: 'Data Viewers', diff --git a/geppetto.js/geppetto-client/package.json b/geppetto.js/geppetto-client/package.json index bf91b7970..4c67a5930 100644 --- a/geppetto.js/geppetto-client/package.json +++ b/geppetto.js/geppetto-client/package.json @@ -1,6 +1,6 @@ { "name": "@metacell/geppetto-meta-client", - "version": "3.0.0", + "version": "3.0.0-alpha.0", "description": "Geppetto web frontend. Geppetto is an open-source platform to build web-based tools to visualize and simulate neuroscience data and models.", "keywords": [ "geppetto", @@ -30,22 +30,14 @@ "publish:yalc": "cd build && yalc publish --push", "watch": "nodemon -e js,ts,jsx,tsx --ignore build --exec \"yarn build:dev && cd build && yalc push --changed\"" }, - "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@material-ui/core": "^4.11.4", - "pako": "^1.0.3", - "react": "^17.0.2", - "react-redux": "^7.2.3", - "react-rnd": "^7.3.0", - "redux": "^4.1.0", - "url-join": "^4.0.0" - }, + "dependencies": {}, "devDependencies": { "@babel/cli": "^7.24.8", "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.3", "@babel/preset-react": "^7.24.7", "@babel/preset-typescript": "^7.24.7", + "@babel/plugin-proposal-class-properties": "^7.18.6", "@eslint/js": "^9.9.0", "babel-jest": "^29.7.0", "babel-plugin-module-resolver": "^5.0.2", @@ -58,8 +50,15 @@ "typescript": "^4.3.2" }, "peerDepedencies": { - "@metacell/geppetto-meta-core": "3.0.0", - "@metacell/geppetto-meta-ui": "3.0.0" + "@metacell/geppetto-meta-core": "^3.0.0-alpha.0", + "@metacell/geppetto-meta-ui": "^3.0.0-alpha.0", + "@material-ui/core": "^4.11.4", + "pako": "^1.0.3", + "react": "^17.0.2", + "react-redux": "^7.2.3", + "react-rnd": "^7.3.0", + "redux": "^4.1.0", + "url-join": "^4.0.0" }, "buildOptions": { "emitEntryPoint": true, diff --git a/geppetto.js/geppetto-client/src/WebsocketMain.js b/geppetto.js/geppetto-client/src/WebsocketMain.js index a57fc31e8..b97eb280e 100644 --- a/geppetto.js/geppetto-client/src/WebsocketMain.js +++ b/geppetto.js/geppetto-client/src/WebsocketMain.js @@ -15,7 +15,7 @@ import Events from './Events'; function createChannel () { // Change link from blank to self for GEPPETTO_CONFIGURATION.embedded environments if (GEPPETTO_CONFIGURATION.embedded && GEPPETTO_CONFIGURATION.embedderURL !== "/" && typeof handleRequest == 'undefined') { - if ($.isArray(GEPPETTO_CONFIGURATION.embedderURL)) { + if (Array.isArray(GEPPETTO_CONFIGURATION.embedderURL)) { window.parent.postMessage({ "command": "ready" }, GEPPETTO_CONFIGURATION.embedderURL[0]); } else { window.parent.postMessage({ "command": "ready" }, GEPPETTO_CONFIGURATION.embedderURL); diff --git a/geppetto.js/geppetto-client/yarn.lock b/geppetto.js/geppetto-client/yarn.lock index 06b652648..58f6dba17 100644 --- a/geppetto.js/geppetto-client/yarn.lock +++ b/geppetto.js/geppetto-client/yarn.lock @@ -41,6 +41,14 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" +"@babel/code-frame@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== + dependencies: + "@babel/highlight" "^7.25.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2": version "7.25.2" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5" @@ -77,6 +85,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" + integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA== + dependencies: + "@babel/types" "^7.25.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" @@ -91,6 +109,13 @@ dependencies: "@babel/types" "^7.24.7" +"@babel/helper-annotate-as-pure@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz#63f02dbfa1f7cb75a9bdb832f300582f30bb8972" + integrity sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA== + dependencies: + "@babel/types" "^7.25.7" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz#37d66feb012024f2422b762b9b2a7cfe27c7fba3" @@ -110,7 +135,20 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.0": +"@babel/helper-create-class-features-plugin@^7.18.6": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz#5d65074c76cae75607421c00d6bd517fe1892d6b" + integrity sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-member-expression-to-functions" "^7.25.7" + "@babel/helper-optimise-call-expression" "^7.25.7" + "@babel/helper-replace-supers" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" + "@babel/traverse" "^7.25.7" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.24.7", "@babel/helper-create-class-features-plugin@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz#a109bf9c3d58dfed83aaf42e85633c89f43a6253" integrity sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ== @@ -159,6 +197,14 @@ "@babel/traverse" "^7.24.8" "@babel/types" "^7.24.8" +"@babel/helper-member-expression-to-functions@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz#541a33b071f0355a63a0fa4bdf9ac360116b8574" + integrity sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/helper-module-imports@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" @@ -184,6 +230,13 @@ dependencies: "@babel/types" "^7.24.7" +"@babel/helper-optimise-call-expression@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz#1de1b99688e987af723eed44fa7fc0ee7b97d77a" + integrity sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng== + dependencies: + "@babel/types" "^7.25.7" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.19.0" resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz" @@ -212,6 +265,15 @@ "@babel/helper-optimise-call-expression" "^7.24.7" "@babel/traverse" "^7.25.0" +"@babel/helper-replace-supers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz#38cfda3b6e990879c71d08d0fef9236b62bd75f5" + integrity sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.25.7" + "@babel/helper-optimise-call-expression" "^7.25.7" + "@babel/traverse" "^7.25.7" + "@babel/helper-simple-access@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" @@ -228,6 +290,14 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-skip-transparent-expression-wrappers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz#382831c91038b1a6d32643f5f49505b8442cb87c" + integrity sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/helper-string-parser@^7.19.4": version "7.19.4" resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz" @@ -238,6 +308,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== +"@babel/helper-string-parser@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" + integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" @@ -248,6 +323,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + "@babel/helper-validator-option@^7.24.7", "@babel/helper-validator-option@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" @@ -289,6 +369,16 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.1.0": version "7.19.6" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.19.6.tgz" @@ -301,6 +391,13 @@ dependencies: "@babel/types" "^7.25.2" +"@babel/parser@^7.25.7": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" + integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== + dependencies: + "@babel/types" "^7.25.8" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3": version "7.25.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz#dca427b45a6c0f5c095a1c639dfe2476a3daba7f" @@ -1064,20 +1161,13 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.15.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.8.4": version "7.19.4" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.4.tgz" integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.8.3": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" - integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.24.7", "@babel/template@^7.25.0", "@babel/template@^7.3.3": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" @@ -1087,6 +1177,15 @@ "@babel/parser" "^7.25.0" "@babel/types" "^7.25.0" +"@babel/template@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769" + integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.1", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.3": version "7.25.3" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490" @@ -1100,6 +1199,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" + integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.3.0", "@babel/types@^7.4.4": version "7.19.4" resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.4.tgz" @@ -1118,16 +1230,20 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@babel/types@^7.25.7", "@babel/types@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" + integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg== + dependencies: + "@babel/helper-string-parser" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@emotion/hash@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" - integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== - "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1429,70 +1545,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@material-ui/core@^4.11.4": - version "4.12.4" - resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.12.4.tgz#4ac17488e8fcaf55eb6a7f5efb2a131e10138a73" - integrity sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ== - dependencies: - "@babel/runtime" "^7.4.4" - "@material-ui/styles" "^4.11.5" - "@material-ui/system" "^4.12.2" - "@material-ui/types" "5.1.0" - "@material-ui/utils" "^4.11.3" - "@types/react-transition-group" "^4.2.0" - clsx "^1.0.4" - hoist-non-react-statics "^3.3.2" - popper.js "1.16.1-lts" - prop-types "^15.7.2" - react-is "^16.8.0 || ^17.0.0" - react-transition-group "^4.4.0" - -"@material-ui/styles@^4.11.5": - version "4.11.5" - resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.5.tgz#19f84457df3aafd956ac863dbe156b1d88e2bbfb" - integrity sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA== - dependencies: - "@babel/runtime" "^7.4.4" - "@emotion/hash" "^0.8.0" - "@material-ui/types" "5.1.0" - "@material-ui/utils" "^4.11.3" - clsx "^1.0.4" - csstype "^2.5.2" - hoist-non-react-statics "^3.3.2" - jss "^10.5.1" - jss-plugin-camel-case "^10.5.1" - jss-plugin-default-unit "^10.5.1" - jss-plugin-global "^10.5.1" - jss-plugin-nested "^10.5.1" - jss-plugin-props-sort "^10.5.1" - jss-plugin-rule-value-function "^10.5.1" - jss-plugin-vendor-prefixer "^10.5.1" - prop-types "^15.7.2" - -"@material-ui/system@^4.12.2": - version "4.12.2" - resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.12.2.tgz#f5c389adf3fce4146edd489bf4082d461d86aa8b" - integrity sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw== - dependencies: - "@babel/runtime" "^7.4.4" - "@material-ui/utils" "^4.11.3" - csstype "^2.5.2" - prop-types "^15.7.2" - -"@material-ui/types@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" - integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== - -"@material-ui/utils@^4.11.3": - version "4.11.3" - resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.3.tgz#232bd86c4ea81dab714f21edad70b7fdf0253942" - integrity sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg== - dependencies: - "@babel/runtime" "^7.4.4" - prop-types "^15.7.2" - react-is "^16.8.0 || ^17.0.0" - "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" resolved "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz" @@ -1578,14 +1630,6 @@ dependencies: "@types/node" "*" -"@types/hoist-non-react-statics@^3.3.0": - version "3.3.1" - resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== - dependencies: - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.4" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" @@ -1617,42 +1661,6 @@ dependencies: undici-types "~6.19.2" -"@types/prop-types@*": - version "15.7.5" - resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== - -"@types/react-redux@^7.1.20": - version "7.1.24" - resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz" - integrity sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ== - dependencies: - "@types/hoist-non-react-statics" "^3.3.0" - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - redux "^4.0.0" - -"@types/react-transition-group@^4.2.0": - version "4.4.5" - resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz" - integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== - dependencies: - "@types/react" "*" - -"@types/react@*": - version "17.0.52" - resolved "https://registry.npmjs.org/@types/react/-/react-17.0.52.tgz" - integrity sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/scheduler@*": - version "0.16.2" - resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== - "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -1990,11 +1998,6 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== -classnames@^2.2.5: - version "2.3.2" - resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== - cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -2004,11 +2007,6 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clsx@^1.0.4: - version "1.2.1" - resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - co@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" @@ -2094,24 +2092,6 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-vendor@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d" - integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ== - dependencies: - "@babel/runtime" "^7.8.3" - is-in-browser "^1.0.2" - -csstype@^2.5.2: - version "2.6.21" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" - integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== - -csstype@^3.0.2: - version "3.1.1" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz" - integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== - debug@^4, debug@^4.3.1, debug@^4.3.2: version "4.3.6" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" @@ -2151,14 +2131,6 @@ diff-sequences@^29.6.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - electron-to-chromium@^1.5.4: version "1.5.13" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" @@ -2572,13 +2544,6 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: - version "3.3.2" - resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" - html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" @@ -2589,11 +2554,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -hyphenate-style-name@^1.0.3: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436" - integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw== - ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" @@ -2691,11 +2651,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-in-browser@^1.0.2, is-in-browser@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" - integrity sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" @@ -3132,7 +3087,7 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: +js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -3157,6 +3112,11 @@ jsesc@^2.5.1: resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" @@ -3192,76 +3152,6 @@ jsonc-parser@^3.0.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== -jss-plugin-camel-case@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz#27ea159bab67eb4837fa0260204eb7925d4daa1c" - integrity sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw== - dependencies: - "@babel/runtime" "^7.3.1" - hyphenate-style-name "^1.0.3" - jss "10.10.0" - -jss-plugin-default-unit@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz#db3925cf6a07f8e1dd459549d9c8aadff9804293" - integrity sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.10.0" - -jss-plugin-global@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz#1c55d3c35821fab67a538a38918292fc9c567efd" - integrity sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.10.0" - -jss-plugin-nested@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz#db872ed8925688806e77f1fc87f6e62264513219" - integrity sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.10.0" - tiny-warning "^1.0.2" - -jss-plugin-props-sort@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz#67f4dd4c70830c126f4ec49b4b37ccddb680a5d7" - integrity sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.10.0" - -jss-plugin-rule-value-function@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz#7d99e3229e78a3712f78ba50ab342e881d26a24b" - integrity sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g== - dependencies: - "@babel/runtime" "^7.3.1" - jss "10.10.0" - tiny-warning "^1.0.2" - -jss-plugin-vendor-prefixer@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz#c01428ef5a89f2b128ec0af87a314d0c767931c7" - integrity sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg== - dependencies: - "@babel/runtime" "^7.3.1" - css-vendor "^2.0.8" - jss "10.10.0" - -jss@10.10.0, jss@^10.5.1: - version "10.10.0" - resolved "https://registry.yarnpkg.com/jss/-/jss-10.10.0.tgz#a75cc85b0108c7ac8c7b7d296c520a3e4fbc6ccc" - integrity sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw== - dependencies: - "@babel/runtime" "^7.3.1" - csstype "^3.0.2" - is-in-browser "^1.1.3" - tiny-warning "^1.0.2" - keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -3345,13 +3235,6 @@ lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.1.0, loose-envify@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" @@ -3486,11 +3369,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - once@^1.3.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" @@ -3557,11 +3435,6 @@ p-try@^2.0.0: resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@^1.0.3: - version "1.0.11" - resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -3651,11 +3524,6 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -popper.js@1.16.1-lts: - version "1.16.1-lts" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" - integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -3678,15 +3546,6 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: - version "15.8.1" - resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - prr@~1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz" @@ -3712,72 +3571,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -re-resizable@4.5.1: - version "4.5.1" - resolved "https://registry.npmjs.org/re-resizable/-/re-resizable-4.5.1.tgz" - integrity sha512-amjlp4IuTSHs4XG1bP5WbAgBDIZitODKIsqcpZsNhEBYYEidol0dlP4S9zHiN3iu6Tff4WfYuruihLgN7RJeQw== - -react-draggable@^3.0.5: - version "3.3.2" - resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-3.3.2.tgz" - integrity sha512-oaz8a6enjbPtx5qb0oDWxtDNuybOylvto1QLydsXgKmwT7e3GXC2eMVDwEMIUYJIFqVG72XpOv673UuuAq6LhA== - dependencies: - classnames "^2.2.5" - prop-types "^15.6.0" - -react-is@^16.13.1, react-is@^16.7.0: - version "16.13.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" - integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== - -"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - react-is@^18.0.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react-redux@^7.2.3: - version "7.2.9" - resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz" - integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== - dependencies: - "@babel/runtime" "^7.15.4" - "@types/react-redux" "^7.1.20" - hoist-non-react-statics "^3.3.2" - loose-envify "^1.4.0" - prop-types "^15.7.2" - react-is "^17.0.2" - -react-rnd@^7.3.0: - version "7.4.3" - resolved "https://registry.npmjs.org/react-rnd/-/react-rnd-7.4.3.tgz" - integrity sha512-TLQ35nqXup7rC63qAETebbO6Znilr20CroTTeAdlYu8nvRSwB7BrmPKZhHB2GgeiSucOoeCyAA9pHPhbMpEd/Q== - dependencies: - re-resizable "4.5.1" - react-draggable "^3.0.5" - -react-transition-group@^4.4.0: - version "4.4.5" - resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - -react@^17.0.2: - version "17.0.2" - resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" @@ -3785,13 +3583,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -redux@^4.0.0, redux@^4.1.0: - version "4.2.0" - resolved "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz" - integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== - dependencies: - "@babel/runtime" "^7.9.2" - regenerate-unicode-properties@^10.1.0: version "10.1.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz" @@ -3809,11 +3600,6 @@ regenerator-runtime@^0.13.4: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz" integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - regenerator-transform@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" @@ -4082,11 +3868,6 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -tiny-warning@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" - integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== - tmpl@1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" @@ -4189,11 +3970,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-join@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - v8-to-istanbul@^9.0.1: version "9.3.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" diff --git a/geppetto.js/geppetto-core/package.json b/geppetto.js/geppetto-core/package.json index 7c526ccb3..41e59e6f7 100644 --- a/geppetto.js/geppetto-core/package.json +++ b/geppetto.js/geppetto-core/package.json @@ -1,6 +1,6 @@ { "name": "@metacell/geppetto-meta-core", - "version": "3.0.0", + "version": "3.0.0-alpha.0", "description": "The core functionality of geppetto-meta to build and simulate neuroscience data and models.", "keywords": [ "geppetto", diff --git a/geppetto.js/geppetto-ui/package.json b/geppetto.js/geppetto-ui/package.json index 98acf8cdf..30a2b767d 100644 --- a/geppetto.js/geppetto-ui/package.json +++ b/geppetto.js/geppetto-ui/package.json @@ -1,6 +1,6 @@ { "name": "@metacell/geppetto-meta-ui", - "version": "3.0.0", + "version": "3.0.0-alpha.0", "description": "React components from geppetto-meta to create neuroscience applications and visualize data.", "repository": { "type": "git", @@ -30,6 +30,7 @@ "publish:yalc": "cd build && yalc publish --push", "watch": "nodemon -e js,ts,tsx,jsx --ignore build --exec \"yarn build:dev && cd build && yalc push --changed\"" }, + "dependencies": {}, "devDependencies": { "@babel/cli": "^7.24.8", "@babel/core": "^7.25.2", @@ -69,7 +70,7 @@ "@fortawesome/react-fontawesome": "^0.1.9", "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", - "@metacell/geppetto-meta-core": "3.0.0", + "@metacell/geppetto-meta-core": "^3.0.0-alpha.0", "aframe": "<1.1.0", "aframe-slice9-component": ">=1.0.0", "ami.js": ">=0.32.0", diff --git a/geppetto.js/geppetto-ui/src/3d-canvas/threeDEngine/TrackballControls.js b/geppetto.js/geppetto-ui/src/3d-canvas/threeDEngine/TrackballControls.js index ed252cbc7..e09697590 100644 --- a/geppetto.js/geppetto-ui/src/3d-canvas/threeDEngine/TrackballControls.js +++ b/geppetto.js/geppetto-ui/src/3d-canvas/threeDEngine/TrackballControls.js @@ -519,6 +519,9 @@ class TrackballControls extends EventDispatcher { scope.setRotation(u[0], u[1], u[2], l); }; + /** + * @type Class + */ this.setRotation = function (x, y, z, radius) { _state = STATE.NONE; _prevState = STATE.NONE; diff --git a/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewer.js b/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewer.js index 60c5846d7..dc2cf0bdf 100644 --- a/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewer.js +++ b/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewer.js @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import { withStyles } from '@material-ui/core'; import { PropTypes } from 'prop-types'; import * as THREE from 'three'; @@ -8,57 +7,27 @@ import { offset } from '../utilities'; import { boundingBoxHelperFactory, VolumeLoader, StackModel } from 'ami.js'; const HelpersBoundingBox = boundingBoxHelperFactory(THREE); -import { - faThLarge, - faSquare, - faExchangeAlt, - faDownload, - faExpandAlt, - faCompressAlt, -} from '@fortawesome/free-solid-svg-icons'; +import { faDownload } from '@fortawesome/free-solid-svg-icons'; import CustomToolbar from '../common/CustomToolbar'; import { createZipFromRemoteFiles } from './util'; import Loader from "../loader/Loader"; -const styles = { - dicomViewer: { - display: 'flex', - flexWrap: 'wrap', - backgroundColor: '#353535', - }, - renderer: { - backgroundColor: '#000', - float: 'left', - width: '50%', - height: '50%', - }, - toolbar: { - padding: '0', - marginLeft: '5px', - }, - toolbarBox: { backgroundColor: 'rgb(0,0,0,0.5);' }, - button: { - padding: '8px', - top: '0', - color: '#fc6320', - }, +const classes = { + dicomViewer: "dicom-viewer", + renderer: "dicom-viewer-renderer", + toolbar: "dicom-viewer-toolbar", + toolbarBox: "dicom-viewer-toolbar-box", + button: "dicom-viewer-button", }; class DicomViewer extends Component { + + animationOn = true; + animationSkipRate = 3; + constructor (props) { super(props); - this.state = { - files: this.extractFilesPath(this.props.data), - mode: this.props.mode === undefined ? 'quad_view' : this.props.mode, - orientation: - this.props.orientation === undefined - ? 'coronal' - : this.props.orientation, - fullScreen: false, - ready: false - }; - // 3d renderer this.r0 = { domClass: 'r0', @@ -70,6 +39,7 @@ class DicomViewer extends Component { controls: null, scene: null, light: null, + parent: this, }; // 2d axial renderer @@ -88,6 +58,8 @@ class DicomViewer extends Component { stackHelper: null, localizerHelper: null, localizerScene: null, + widgets: [], + parent: this, }; // 2d sagittal renderer @@ -106,6 +78,8 @@ class DicomViewer extends Component { stackHelper: null, localizerHelper: null, localizerScene: null, + widgets: [], + parent: this, }; // 2d coronal renderer @@ -124,220 +98,279 @@ class DicomViewer extends Component { stackHelper: null, localizerHelper: null, localizerScene: null, + widgets: [], + parent: this, }; - this.changeMode = this.changeMode.bind(this); - this.changeOrientation = this.changeOrientation.bind(this); - this.download = this.download.bind(this); - this.restore = this.restore.bind(this); - this.fullScreen = this.fullScreen.bind(this); + this.state = { ready: false } + this.containerRef = React.createRef(); + + this.onRender = this.props.onRender; + this.drag = false; + this.animationSkipRate = this.props.animationSkipRate || 3; + + this.startAnimation = this.startAnimation.bind(this); + this.stopAnimation = this.stopAnimation.bind(this); + this.centerOn = this.centerOn.bind(this); } extractFilesPath (data) { - let files; - if (data !== undefined) { - if (data.getMetaType === undefined) { - files = data; - } else if (data.getMetaType() === 'Instance') { - if (data.getVariable().getInitialValues()[0].value.format === 'NIFTI') { - files = data.getVariable().getInitialValues()[0].value.data; - } else if ( - data.getVariable().getInitialValues()[0].value.format === 'DCM' - ) { - // todo: What do we do here? - } + if (!data) { + return undefined; + } + if (!data.getMetaType) { + return data; + } + if (data.getMetaType() === 'Instance') { + const value = data.getVariable().getInitialValues()[0].value; + switch (value.format){ + case 'NIFTI': + return value.data + case 'DCM': + break; // todo: what do we do here? } } - return files; + return undefined; } - loadModel () { - if (this.state.files !== undefined && null != this.state.files) { - this.ready = false; - const _this = this; + loadModel (modelData) { + const data = this.extractFilesPath(modelData) + if (!data) { + return + } + this.ready = false + const _this = this; + + let animationCount = 0; + /** + * Init the view + */ + function init () { /** - * Init the quadview + * Called on each animation frame */ - function init () { - /** - * Called on each animation frame - */ - function animate () { + function animate () { + if ( _this.ready && _this.animationOn && animationCount++ % _this.animationSkipRate === 0) { // we are ready when both meshes have been loaded - if (_this.ready) { - if ( - (_this.state.mode === 'single_view' - && _this.state.orientation === '3d') - || _this.state.mode === 'quad_view' - ) { - // render - _this.r0.controls.update(); - _this.r0.light.position.copy(_this.r0.camera.position); - _this.r0.renderer.render(_this.r0.scene, _this.r0.camera); - } + const { mode, orientation } = _this.props; + const shouldRenderPlane = plane => mode === 'quad_view' || orientation === plane; + const shouldRender3D = shouldRenderPlane('3d'); + + const planesSetup = { + 'r1': 'sagittal', + 'r2': 'axial', + 'r3': 'coronal' + }; + + if (shouldRender3D) { + // render + _this.r0.controls.update(); + _this.r0.light.position.copy(_this.r0.camera.position); + _this.r0.renderer.render(_this.r0.scene, _this.r0.camera); + _this.r0.scene.background = new THREE.Color("#353535"); + } - if ( - (_this.state.mode === 'single_view' - && _this.state.orientation === 'sagittal') - || _this.state.mode === 'quad_view' - ) { - _this.r1.controls.update(); - // r1 - _this.r1.renderer.clear(); - _this.r1.renderer.render(_this.r1.scene, _this.r1.camera); - - // localizer - _this.r1.renderer.clearDepth(); - _this.r1.renderer.render( - _this.r1.localizerScene, - _this.r1.camera - ); + for (const [planeName, planeOrientation] of Object.entries(planesSetup)) { + if (!shouldRenderPlane(planeOrientation)) { + continue } - - if ( - (_this.state.mode === 'single_view' - && _this.state.orientation === 'axial') - || _this.state.mode === 'quad_view' - ) { - _this.r2.controls.update(); - // r2 - _this.r2.renderer.clear(); - _this.r2.renderer.render(_this.r2.scene, _this.r2.camera); - // localizer - _this.r2.renderer.clearDepth(); - _this.r2.renderer.render( - _this.r2.localizerScene, - _this.r2.camera - ); + const plane = _this[planeName] + const camera = plane.camera + const renderer = plane.renderer + plane.controls.update(); + + // clear renderer + renderer.clear(); + + plane.renderer.render(plane.scene, camera); + plane.scene.background = new THREE.Color("#353535"); + + // localizer + renderer.clearDepth(); + renderer.render( + plane.localizerScene, + camera + ); + + // Widgets + for (const widget of plane.widgets) { + widget.update(); } + } - if ( - (_this.state.mode === 'single_view' - && _this.state.orientation === 'coronal') - || _this.state.mode === 'quad_view' - ) { - _this.r3.controls.update(); - // r3 - _this.r3.renderer.clear(); - _this.r3.renderer.render(_this.r3.scene, _this.r3.camera); - // localizer - _this.r3.renderer.clearDepth(); - _this.r3.renderer.render( - _this.r3.localizerScene, - _this.r3.camera - ); - } + if (_this.onRender) { + _this.onRender([_this.r0, _this.r1, _this.r2, _this.r3]); } + } - // request new frame - requestAnimationFrame(function () { - animate(); + // request new frame + requestAnimationFrame(animate); + } + + // renderers + DicomViewerUtils.initRenderer3D(_this.r0, _this.containerRef.current); + DicomViewerUtils.initRenderer2D(_this.r1, _this.containerRef.current); + DicomViewerUtils.initRenderer2D(_this.r2, _this.containerRef.current); + DicomViewerUtils.initRenderer2D(_this.r3, _this.containerRef.current); + + // start rendering loop + animate(); + } + + // init threeJS + init(); + + /* + * load sequence for each file + * instantiate the loader + * it loads and parses the dicom image + */ + let loader = new VolumeLoader(); + loader + .load(this.extractFilesPath(data)) + .then(() => { + const { applySegmentationLUT } = _this.props; + + const series = loader.data[0].mergeSeries(loader.data)[0]; + loader.free(); + loader = null; + // get first stack from series + const stack = series.stack[0]; + stack.prepare(); + + // center 3d camera/control on the stack + const centerLPS = stack.worldCenter(); + _this.r0.camera.lookAt(centerLPS.x, centerLPS.y, centerLPS.z); + _this.r0.camera.updateProjectionMatrix(); + _this.r0.controls.target.set(centerLPS.x, centerLPS.y, centerLPS.z); + + // bounding box + const boxHelper = new HelpersBoundingBox(stack); + _this.r0.scene.add(boxHelper); + + // Init the 2D planes stack helpers + DicomViewerUtils.initHelpersStack(_this.r1, stack); + DicomViewerUtils.initHelpersStack(_this.r2, stack); + DicomViewerUtils.initHelpersStack(_this.r3, stack); + + // Init the 2D planes segmentation LUT if necessary + if (applySegmentationLUT) { + DicomViewerUtils.initSegmentationLUT(_this.r1, stack, _this.r1.stackHelper); + DicomViewerUtils.initSegmentationLUT(_this.r2, stack, _this.r2.stackHelper); + DicomViewerUtils.initSegmentationLUT(_this.r3, stack, _this.r3.stackHelper); + } + + const planesSetup = { + 'r1': ['r2', 'r3'], // red slice + 'r2': ['r1', 'r3'], // yellow slice + 'r3': ['r1', 'r2'] // green slice + } + + // Initialize 2D planes localizer (cross on 2D planes) + for (const [planeName, linkedPlaneNames] of Object.entries(planesSetup)) { + const plane = _this[planeName] + + // Add each 2D plane to the 3D scene + _this.r0.scene.add(plane.scene); + + /* + * Init the localizer + * create new mesh with Localizer shaders + */ + const planeEquation = plane.stackHelper.slice.cartesianEquation(); + + /* + * create new mesh with Localizer shaders + * compute the dependent stack helper + */ + const linkedPlanes = linkedPlaneNames.map(name => { + const linkedStackHelper = _this[name].stackHelper; + return ({ + plane: linkedStackHelper.slice.cartesianEquation(), + color: new THREE.Color(linkedStackHelper.borderColor) + }) }); + + DicomViewerUtils.initHelpersLocalizer(plane, stack, planeEquation, linkedPlanes); } - // renderers - DicomViewerUtils.initRenderer3D(_this.r0, _this.containerRef.current); - DicomViewerUtils.initRenderer2D(_this.r1, _this.containerRef.current); - DicomViewerUtils.initRenderer2D(_this.r2, _this.containerRef.current); - DicomViewerUtils.initRenderer2D(_this.r3, _this.containerRef.current); + _this.configureEvents(); + _this.updateLayout(_this.props.mode); + _this.ready = true; + _this.setState({ ready: true }); + _this.props.onLoaded(); + }) + .catch(error => { + window.console.log('oops... something went wrong...'); + window.console.log(error); + }); + } - // start rendering loop - animate(); - } + updateLayout (mode) { + if (mode === 'single_view') { + this.setSingleLayout(this.props.orientation); + } else { + this.setQuadLayout(); + } + } + + centerOn (plane, point) { + if (!plane.stackHelper) { + return + } + const stackHelper = plane.stackHelper + const ijk = StackModel.worldToData( + stackHelper.stack, + point + ); + this.r1.stackHelper.index = ijk.getComponent( + (this.r1.stackHelper.orientation + 2) % 3 + ); + this.r2.stackHelper.index = ijk.getComponent( + (this.r2.stackHelper.orientation + 2) % 3 + ); + this.r3.stackHelper.index = ijk.getComponent( + (this.r3.stackHelper.orientation + 2) % 3 + ); + + DicomViewerUtils.updateLocalizer(this.r2, [ + this.r1.localizerHelper, + this.r3.localizerHelper, + ]); + DicomViewerUtils.updateLocalizer(this.r1, [ + this.r2.localizerHelper, + this.r3.localizerHelper, + ]); + DicomViewerUtils.updateLocalizer(this.r3, [ + this.r1.localizerHelper, + this.r2.localizerHelper, + ]); + } - // init threeJS - init(); + clickedOnPoint (event, callback) { + const canvas = event.srcElement.parentElement; + const id = event.target.id; + const mouse = { + x: ((event.clientX - offset(canvas).left) / canvas.clientWidth) * 2 - 1, + y: + -((event.clientY - offset(canvas).top) / canvas.clientHeight) * 2 + 1, + }; - /* - * load sequence for each file - * instantiate the loader - * it loads and parses the dicom image - */ - let loader = new VolumeLoader(); - loader - .load(this.state.files) - .then(function () { - let series = loader.data[0].mergeSeries(loader.data)[0]; - loader.free(); - loader = null; - // get first stack from series - let stack = series.stack[0]; - stack.prepare(); - - // center 3d camera/control on the stack - let centerLPS = stack.worldCenter(); - _this.r0.camera.lookAt(centerLPS.x, centerLPS.y, centerLPS.z); - _this.r0.camera.updateProjectionMatrix(); - _this.r0.controls.target.set(centerLPS.x, centerLPS.y, centerLPS.z); - - // bouding box - let boxHelper = new HelpersBoundingBox(stack); - _this.r0.scene.add(boxHelper); - - // red slice - DicomViewerUtils.initHelpersStack(_this.r1, stack); - _this.r0.scene.add(_this.r1.scene); - - // yellow slice - DicomViewerUtils.initHelpersStack(_this.r2, stack); - _this.r0.scene.add(_this.r2.scene); - - // green slice - DicomViewerUtils.initHelpersStack(_this.r3, stack); - _this.r0.scene.add(_this.r3.scene); - - // create new mesh with Localizer shaders - let plane1 = _this.r1.stackHelper.slice.cartesianEquation(); - let plane2 = _this.r2.stackHelper.slice.cartesianEquation(); - let plane3 = _this.r3.stackHelper.slice.cartesianEquation(); - - // localizer red slice - DicomViewerUtils.initHelpersLocalizer(_this.r1, stack, plane1, [ - { - plane: plane2, - color: new THREE.Color(_this.r2.stackHelper.borderColor), - }, - { - plane: plane3, - color: new THREE.Color(_this.r3.stackHelper.borderColor), - }, - ]); - - // localizer yellow slice - DicomViewerUtils.initHelpersLocalizer(_this.r2, stack, plane2, [ - { - plane: plane1, - color: new THREE.Color(_this.r1.stackHelper.borderColor), - }, - { - plane: plane3, - color: new THREE.Color(_this.r3.stackHelper.borderColor), - }, - ]); - - // localizer green slice - DicomViewerUtils.initHelpersLocalizer(_this.r3, stack, plane3, [ - { - plane: plane1, - color: new THREE.Color(_this.r1.stackHelper.borderColor), - }, - { - plane: plane2, - color: new THREE.Color(_this.r2.stackHelper.borderColor), - }, - ]); - - _this.configureEvents(); - _this.ready = true; - _this.props.onLoaded(_this.r0.scene) - _this.setState({ ready: true }) - }) - .catch(function (error) { - window.console.log('oops... something went wrong...'); - window.console.log(error); - }); + const plane = this[`r${id}`] + if (!plane) { + return + } + const camera = plane.camera; + // const stackHelper = plane.stackHelper; + const scene = plane.scene; + const raycaster = new THREE.Raycaster(); + raycaster.setFromCamera(mouse, camera); + + const intersects = raycaster.intersectObjects(scene.children, true); + if (intersects.length > 0) { + callback(plane, intersects[0].point, event) } } @@ -345,72 +378,11 @@ class DicomViewer extends Component { const _this = this; function goToPoint (event) { - const canvas = event.srcElement.parentElement; - const id = event.target.id; - const mouse = { - x: ((event.clientX - offset(canvas).left) / canvas.clientWidth) * 2 - 1, - y: - -((event.clientY - offset(canvas).top) / canvas.clientHeight) * 2 + 1, - }; - - let camera = null; - let stackHelper = null; - let scene = null; - switch (id) { - case '0': - camera = _this.r0.camera; - stackHelper = _this.r1.stackHelper; - scene = _this.r0.scene; - break; - case '1': - camera = _this.r1.camera; - stackHelper = _this.r1.stackHelper; - scene = _this.r1.scene; - break; - case '2': - camera = _this.r2.camera; - stackHelper = _this.r2.stackHelper; - scene = _this.r2.scene; - break; - case '3': - camera = _this.r3.camera; - stackHelper = _this.r3.stackHelper; - scene = _this.r3.scene; - break; - } + _this.clickedOnPoint(event, _this.centerOn) + } - const raycaster = new THREE.Raycaster(); - raycaster.setFromCamera(mouse, camera); - - const intersects = raycaster.intersectObjects(scene.children, true); - if (intersects.length > 0) { - let ijk = StackModel.worldToData( - stackHelper.stack, - intersects[0].point - ); - _this.r1.stackHelper.index = ijk.getComponent( - (_this.r1.stackHelper.orientation + 2) % 3 - ); - _this.r2.stackHelper.index = ijk.getComponent( - (_this.r2.stackHelper.orientation + 2) % 3 - ); - _this.r3.stackHelper.index = ijk.getComponent( - (_this.r3.stackHelper.orientation + 2) % 3 - ); - - DicomViewerUtils.updateLocalizer(_this.r2, [ - _this.r1.localizerHelper, - _this.r3.localizerHelper, - ]); - DicomViewerUtils.updateLocalizer(_this.r1, [ - _this.r2.localizerHelper, - _this.r3.localizerHelper, - ]); - DicomViewerUtils.updateLocalizer(_this.r3, [ - _this.r1.localizerHelper, - _this.r2.localizerHelper, - ]); - } + function pointClickedOn (event) { + _this.clickedOnPoint(event, _this.props.pointClickedOn) } function goToSingleView (event) { @@ -436,18 +408,20 @@ class DicomViewer extends Component { } } - function toggleMode (event) { - if (_this.state.mode === 'single_view') { - _this.changeMode(); - } else { - goToSingleView(event); - } - } + /* + * function toggleMode (event) { + * if (_this.props.mode === 'single_view') { + * _this.changeMode(); + * } else { + * goToSingleView(event); + * } + * } + */ function onScroll (event) { - const id = event.target.domElement.id; + const view = event.target.domElement.dataset.view; let stackHelper = null; - switch (id) { + switch (view) { case 'r1': stackHelper = _this.r1.stackHelper; break; @@ -483,15 +457,12 @@ class DicomViewer extends Component { _this.r1.localizerHelper, _this.r2.localizerHelper, ]); + return true; } function performEventAction (action, event) { // Check if it is a already defined action or a external one - if ( - action === 'goToPoint' - || action === 'goToSingleView' - || action === 'toggleMode' - ) { + if (action === 'goToPoint' || action === 'goToSingleView' || action === 'toggleMode' || action === 'pointClickedOn') { eval(action + '(event)'); } else { action(event, this); @@ -499,24 +470,20 @@ class DicomViewer extends Component { } function eventHandling (event) { - if (event.type === 'click' && _this.props.onClick !== undefined) { - performEventAction(_this.props.onClick, event); - } else if ( - event.type === 'click' - && (event.ctrlKey || event.metaKey) - && _this.props.onCtrlClick !== undefined - ) { + if (_this.drag) { + return + } + if (event.type === 'mouseup' && event.which === 3 && _this.props.onRightClick) { + performEventAction(_this.props.onRightClick, event); + } + const isClick = event.type === 'click'; + if (isClick && (event.ctrlKey || event.metaKey) && _this.props.onCtrlClick) { performEventAction(_this.props.onCtrlClick, event); - } else if ( - event.type === 'click' - && event.shiftKey - && _this.props.onShiftClick !== undefined - ) { + } else if (isClick && event.shiftKey && _this.props.onShiftClick) { performEventAction(_this.props.onShiftClick, event); - } else if ( - event.type === 'dblclick' - && _this.props.onDoubleClick !== undefined - ) { + } else if (isClick && _this.props.onClick) { + performEventAction(_this.props.onClick, event); + } else if (event.type === 'dblclick' && _this.props.onDoubleClick) { performEventAction(_this.props.onDoubleClick, event); } } @@ -533,10 +500,28 @@ class DicomViewer extends Component { this.r2.domElement.addEventListener('click', eventHandling); this.r3.domElement.addEventListener('click', eventHandling); + this.r0.domElement.addEventListener('mouseup', eventHandling); + this.r1.domElement.addEventListener('mouseup', eventHandling); + this.r2.domElement.addEventListener('mouseup', eventHandling); + this.r3.domElement.addEventListener('mouseup', eventHandling); + // event listeners on scrol this.r1.controls.addEventListener('OnScroll', onScroll); this.r2.controls.addEventListener('OnScroll', onScroll); this.r3.controls.addEventListener('OnScroll', onScroll); + + // event listener on mouse down/up/move to detect drag on all views + for (const subref of ['r0', 'r1', 'r2', 'r3']) { + this[subref].domElement.addEventListener('mousedown', () => { + this.drag = false; return false + }); + this[subref].domElement.addEventListener('mousemove', () => { + this.drag = true + }); + this[subref].domElement.addEventListener('mouseout', () => { + this.drag = false + }); + } } setQuadLayout () { @@ -549,36 +534,21 @@ class DicomViewer extends Component { DicomViewerUtils.windowResize2D(this.r3); } - setSingleLayout () { - let rendererObj; - switch (this.state.orientation) { + setSingleLayout (orientation) { + switch (orientation) { case '3d': - rendererObj = this.r0; + DicomViewerUtils.windowResize3D(this.r0); break; case 'sagittal': - rendererObj = this.r1; + DicomViewerUtils.windowResize2D(this.r1); break; case 'axial': - rendererObj = this.r2; + DicomViewerUtils.windowResize2D(this.r2); break; case 'coronal': - rendererObj = this.r3; + DicomViewerUtils.windowResize2D(this.r3); break; } - - if (this.state.orientation === '3d') { - DicomViewerUtils.windowResize3D(rendererObj); - } else { - DicomViewerUtils.windowResize2D(rendererObj); - } - } - - setLayout () { - if (this.state.mode === 'single_view') { - this.setSingleLayout(); - } else { - this.setQuadLayout(); - } } componentWillUnmount () { @@ -589,81 +559,60 @@ class DicomViewer extends Component { } componentDidMount () { - this.loadModel(); + this.loadModel(this.props.data); } - componentDidUpdate (prevProps, prevState, snapshot) { - if (prevState.files !== this.state.files) { - this.loadModel(); - } else { - this.setLayout(); - } - } + componentDidUpdate (prevProps, prevState) { - changeMode () { - if (this.state.mode === 'single_view') { - this.setState({ mode: 'quad_view' }); - } else { - this.setState({ mode: 'single_view' }); - } - } + if (this.props.mode !== prevProps.mode + || this.props.orientation !== prevProps.orientation + || (this.props.update > 1 && this.props.update !== prevProps.update) + || prevState.ready !== this.state.ready && !prevState.ready) { + try { + this.updateLayout(this.props.mode); + } catch (e) { + // not ready yet + } - changeOrientation () { - let newOrientation; - switch (this.state.orientation) { - case 'coronal': - newOrientation = 'sagittal'; - break; - case 'sagittal': - newOrientation = 'axial'; - break; - case 'axial': - newOrientation = '3d'; - break; - case '3d': - newOrientation = 'coronal'; - break; - default: - break; } - this.setState({ orientation: newOrientation }); + } - download () { - createZipFromRemoteFiles(this.state.files, 'data.zip'); + shouldComponentUpdate (nextProps, nextState) { + this.animationOn = true; + return nextProps.data !== this.props.data + || nextProps.update !== this.props.update + || nextProps.fullScreen !== this.props.fullScreen + || nextProps.mode !== this.props.mode + || nextProps.orientation !== this.props.orientation + || nextState.ready !== this.state.ready && nextState.ready } - restore () { - this.setState({ fullScreen: false }); + stopAnimation () { + this.animationOn = false + } + startAnimation () { + this.animationOn = true } - fullScreen () { - this.setState({ fullScreen: true }); + download () { + createZipFromRemoteFiles(this.props.data, !Array.isArray(this.props.data) ? this.props.data : "data.zip"); } getCustomButtons () { const customButtons = []; + const toolbarButtons = this.props.toolbarButtons; - if (this.state.mode === 'single_view') { - customButtons.push({ - icon: faThLarge, - id: 'Multi View', - tooltip: 'Multi View', - action: this.changeMode, - }); - customButtons.push({ - icon: faExchangeAlt, - id: 'Change Orientation', - tooltip: 'Change Orientation', - action: this.changeOrientation, - }); + const addButtons = buttons => { + if (! buttons) { + return ; + } + customButtons.push(...buttons.map(b => ({ ...b, id: b.tooltip }))) + } + if (this.props.mode == 'single_view') { + addButtons(toolbarButtons?.single_view) } else { - customButtons.push({ - icon: faSquare, - id: 'Single View', - tooltip: 'Single View', - action: this.changeMode, - }); + addButtons(toolbarButtons?.quad_view) } if (this.props.showDownloadButton) { @@ -675,28 +624,17 @@ class DicomViewer extends Component { }); } - if (this.state.fullScreen) { - customButtons.push({ - icon: faCompressAlt, - id: 'Restore', - tooltip: 'Restore', - action: this.restore, - }); + if (this.props.fullScreen) { + addButtons(toolbarButtons?.fullScreen) } else { - customButtons.push({ - icon: faExpandAlt, - id: 'Maximize', - tooltip: 'Maximize', - action: this.fullScreen, - }); + addButtons(toolbarButtons?.minimized) } return customButtons; } render () { - const { classes, toolbarOptions, loaderOptions } = this.props; - const { fullScreen } = this.state; + const { toolbarOptions, loaderOptions, mode, orientation, id, fullScreen } = this.props; const customButtons = this.getCustomButtons(); const containerStyle = fullScreen @@ -714,13 +652,17 @@ class DicomViewer extends Component { width: '100%', }; + const displayView = o => mode === 'single_view' && orientation === o + const display3DView = displayView('3d') + const doNotDisplay = o => mode === 'single_view' && orientation !== o + const showLoader = loaderOptions && loaderOptions.showLoader const loader = loaderOptions && loaderOptions.instance ? ( - ) : {!this.state.ready && showLoader && loader} {toolbar}
+
+
+
- ) + ); } } -DicomViewer.defaultProps = { +const Wrapper = props => ; + +Wrapper.defaultProps = { onLoaded: () => {}, - mode: 'coronal', - orientation: 'goToPoint', + mode: 'quad_view', + orientation: '3d', onClick: 'goToPoint', onCtrlClick: 'goToPoint', onShiftClick: 'goToPoint', onDoubleClick: 'goToPoint', + onRightClick: undefined, showDownloadButton: false, toolbarOptions: null, loaderOptions: { showLoader: true } }; -DicomViewer.propTypes = { +Wrapper.propTypes = { /** * Component identifier */ @@ -867,39 +789,54 @@ DicomViewer.propTypes = { /** * Initial view mode: 'single_view' or 'quad_view' */ - mode: PropTypes.string, + mode: PropTypes.oneOf(['single_view', 'quad_view']), + /** + * Display the dicom viewer in full screen + */ + fullScreen: PropTypes.bool, /** - * Initial orientation view: 'coronal', 'axial' or 'sagittal' + * Initial orientation view: '3d', 'coronal', 'axial' or 'sagittal' */ - orientation: PropTypes.string, + orientation: PropTypes.oneOf(['3d', 'coronal', 'axial', 'sagittal']), /** - * Action to perform on click: 'goToPoint', 'goToSingleView', 'toggleMode', or other + * Action to perform on click: 'goToPoint', or custom action */ onClick: PropTypes.oneOfType([ - PropTypes.oneOf(['goToPoint', 'goToSingleView', 'toggleMode']), + PropTypes.oneOf(['goToPoint']), PropTypes.func, ]), /** - * Action to performe on Ctrl click: 'goToPoint', 'goToSingleView', 'toggleMode', or other + * Action to performe on Ctrl click: 'goToPoint' or custom action */ onCtrlClick: PropTypes.oneOfType([ - PropTypes.oneOf(['goToPoint', 'goToSingleView', 'toggleMode']), + PropTypes.oneOf(['goToPoint']), PropTypes.func, ]), /** - * Action to performe on Shift click: 'goToPoint', 'goToSingleView', 'toggleMode', or other + * Action to performe on Shift click: 'goToPoint' or custom action */ onShiftClick: PropTypes.oneOfType([ - PropTypes.oneOf(['goToPoint', 'goToSingleView', 'toggleMode']), + PropTypes.oneOf(['goToPoint']), + PropTypes.func, + ]), + /** + * Action to performe on right click: 'goToPoint' or custom action + */ + onRightClick: PropTypes.oneOfType([ + PropTypes.oneOf(['goToPoint']), PropTypes.func, ]), /** - * Action to performe on double click: 'goToPoint', 'goToSingleView', 'toggleMode', or other + * Action to performe on double click: 'goToPoint' or custom action */ onDoubleClick: PropTypes.oneOfType([ - PropTypes.oneOf(['goToPoint', 'goToSingleView', 'toggleMode']), + PropTypes.oneOf(['goToPoint']), PropTypes.func, ]), + /** + * Enables segmentation LUT + */ + applySegmentationLUT: PropTypes.bool, /** * Bool that defines the showing or not of the download button */ @@ -978,8 +915,80 @@ DicomViewer.propTypes = { */ backgroundColor: PropTypes.string, }), - }), + /** + * Buttons and action to add depending in the view mode/full screen + */ + toolbarButtons: PropTypes.shape({ + /** + * Buttons to display if the view is minimized + */ + minimized: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })), + /** + * Buttons to display if the view is full screen + */ + fullScreen: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })), + /** + * Buttons to display if the view is in single view mode + */ + single_view: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })), + /** + * Buttons to display if the view in quad view mode + */ + quad_view: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })) + }) }; -export default withStyles(styles)(DicomViewer); +export default Wrapper diff --git a/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewerUtils.js b/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewerUtils.js index 61eb9fb38..bbfa08478 100644 --- a/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewerUtils.js +++ b/geppetto.js/geppetto-ui/src/dicom-viewer/DicomViewerUtils.js @@ -58,6 +58,7 @@ const dicomViewerUtils = { rendererObj.localizerHelper.remove(rendererObj.localizerHelper._mesh); rendererObj.localizerHelper._mesh.geometry.dispose(); rendererObj.localizerHelper._mesh.geometry = null; + rendererObj.localizerHelper._mesh.material.dispose(); rendererObj.localizerHelper._mesh = null; } } diff --git a/geppetto.js/geppetto-ui/src/dicom-viewer/README.md b/geppetto.js/geppetto-ui/src/dicom-viewer/README.md index 0ec154eec..6e9d1c0c2 100644 --- a/geppetto.js/geppetto-ui/src/dicom-viewer/README.md +++ b/geppetto.js/geppetto-ui/src/dicom-viewer/README.md @@ -4,8 +4,8 @@ Viewer for any images in the DICOM format. Features: * Tiled view with separated orientations at a glance -* Combined view with all orientations -* Single orientation view +* Combined view with all orientations +* Single orientation view * Navigate slice stack for each orientation (mouse wheel) * Rotate (Left-click + drag) * Download data (download button) @@ -16,9 +16,11 @@ dicom-viewer/DicomViewer ## Examples -### Dicom Viewer Example +### Base Dicom Viewer Example The following example shows a brain MRI in DICOM format acquired at The Brain Observatory. +This component is the base component and is higly configurable. +The component gives all you need to implement your own buttons, but do not provide a default implementation for the buttons. ``` dicom-viewer/DicomViewerExample diff --git a/geppetto.js/geppetto-ui/src/dicom-viewer/preconf/DicomViewer.js b/geppetto.js/geppetto-ui/src/dicom-viewer/preconf/DicomViewer.js new file mode 100644 index 000000000..e0d6c6d7d --- /dev/null +++ b/geppetto.js/geppetto-ui/src/dicom-viewer/preconf/DicomViewer.js @@ -0,0 +1,339 @@ +import React, { useState } from 'react'; +import { PropTypes } from 'prop-types'; +import BaseDicomViewer from '@metacell/geppetto-meta-ui/dicom-viewer/DicomViewer'; +import { + faThLarge, + faSquare, + faExchangeAlt, + faExpandAlt, + faCompressAlt, +} from '@fortawesome/free-solid-svg-icons'; + + +const DicomViewer = props => { + const [fullScreen, setFullScreen] = useState(props.fullScreen); + const [mode, setMode] = useState(props.mode); + const [orientation, setOrientation] = useState(props.orientation); + + const changeMode = () => { + if (mode === 'single_view') { + setMode('quad_view'); + } else { + setMode('single_view'); + } + } + + const changeOrientation = () => { + let newOrientation; + switch (orientation) { + case 'coronal': + newOrientation = 'sagittal'; + break; + case 'sagittal': + newOrientation = 'axial'; + break; + case 'axial': + newOrientation = '3d'; + break; + case '3d': + default: + newOrientation = 'coronal'; + break; + } + setOrientation(newOrientation); + } + + const restore = () => { + setFullScreen(false); + } + + const goFullScreen = () => { + setFullScreen(true); + } + + const goToSingleView = event => { + const id = event.target.id; + let orientation = null; + switch (id) { + case '0': + orientation = '3d'; + break; + case '1': + orientation = 'sagittal'; + break; + case '2': + orientation = 'axial'; + break; + case '3': + orientation = 'coronal'; + break; + } + if (orientation != null) { + setMode('single_view'); + setOrientation(orientation); + } + } + + const toggleMode = event => { + if (mode === 'single_view') { + changeMode(); + } else { + goToSingleView(event); + } + } + + return +}; + +DicomViewer.defaultProps = { + onLoaded: () => {}, + mode: 'quad_view', + orientation: '3d', + onClick: 'goToPoint', + onCtrlClick: undefined, + onShiftClick: undefined, + onDoubleClick: undefined, + onRightClick: undefined, + showDownloadButton: false, + toolbarOptions: null, + loaderOptions: { showLoader: true } +}; +DicomViewer.propTypes = BaseDicomViewer.propTypes; +DicomViewer.propTypes = { + /** + * Component identifier + */ + id: PropTypes.string.isRequired, + /** + * Path/URL to file (f.e. "/path/to/my/file.gz") + */ + data: PropTypes.string.isRequired, + /** + * Initial view mode: 'single_view' or 'quad_view' + */ + mode: PropTypes.oneOf(['single_view', 'quad_view']), + /** + * Display the dicom viewer in full screen + */ + fullScreen: PropTypes.bool, + /** + * Initial orientation view: '3d', 'coronal', 'axial' or 'sagittal' + */ + orientation: PropTypes.oneOf(['3d', 'coronal', 'axial', 'sagittal']), + /** + * Action to perform on click: 'goToPoint' or custom action, default is 'goToPoint' + */ + onClick: PropTypes.oneOfType([ + PropTypes.oneOf(['goToPoint']), + PropTypes.func, + ]), + /** + * Action to performe on Ctrl click: 'goToPoint' or custom action, default is a "go to single view" action + */ + onCtrlClick: PropTypes.oneOfType([ + PropTypes.oneOf(['goToPoint']), + PropTypes.func, + ]), + /** + * Action to performe on Shift click: 'goToPoint' or custom action, default is undefined + */ + onShiftClick: PropTypes.oneOfType([ + PropTypes.oneOf(['goToPoint']), + PropTypes.func, + ]), + /** + * Action to performe on right click: 'goToPoint' or custom action, default is undefined + */ + onRightClick: PropTypes.oneOfType([ + PropTypes.oneOf(['goToPoint']), + PropTypes.func, + ]), + /** + * Action to performe on double click: 'goToPoint' or custom action, default is undefined + */ + onDoubleClick: PropTypes.oneOfType([ + PropTypes.oneOf(['goToPoint']), + PropTypes.func, + ]), + /** + * Enables segmentation LUT + */ + applySegmentationLUT: PropTypes.bool, + /** + * Bool that defines the showing or not of the download button + */ + showDownloadButton: PropTypes.bool, + /** + * Callback function to be called after load is complete + */ + onLoaded: PropTypes.func, + /** + * Options to customize the toolbar + */ + toolbarOptions: PropTypes.shape({ + /** + * Reference to toolbar component + */ + instance: PropTypes.elementType, + /** + * Custom toolbar props + */ + props: PropTypes.shape({}), + /** + * Styles to be applied to the toolbar container + */ + containerStyles: PropTypes.shape({}), + /** + * Styles to be applied to the toolbar + */ + toolBarClassName: PropTypes.shape({}), + /** + * Styles to be applied to the inner div + */ + innerDivStyles: PropTypes.shape({}), + /** + * Styles to be applied to the buttons + */ + buttonStyles: PropTypes.shape({}), + }), + /** + * Options to customize the loader + */ + loaderOptions: PropTypes.shape({ + /** + * Reference to toolbar component + */ + instance: PropTypes.elementType, + /** + * Custom loader props + */ + props: PropTypes.shape({}), + /** + * Bool to control the use of the loader + */ + showLoader: PropTypes.bool, + /** + * Function to handle the close of the Loader + */ + handleClose: PropTypes.func, + /** + * Array of Custom messages to display + */ + messages: PropTypes.array, + /** + * Number of milliseconds between custom messages + */ + messagesInterval: PropTypes.number, + /** + * Number of the progress value to show in linear determinate (in percentage) + */ + elapsed: PropTypes.number, + /** + * Style to be applied to the Loader background + */ + backgroundStyle: PropTypes.shape({ + /** + * Loader's background color. Defaults to rgba(255,142,0,0.1) + */ + backgroundColor: PropTypes.string, + }), + }), + /** + * Buttons and action to add depending in the view mode/full screen + */ + toolbarButtons: PropTypes.shape({ + /** + * Buttons to display if the view is minimized + */ + minimized: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })), + /** + * Buttons to display if the view is full screen + */ + fullScreen: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })), + /** + * Buttons to display if the view is in single view mode + */ + single_view: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })), + /** + * Buttons to display if the view in quad view mode + */ + quad_view: PropTypes.arrayOf(PropTypes.shape({ + /** + * The button icon + */ + icon: PropTypes.string, + /** + * The tooltip of the button + */ + tooltip: PropTypes.string, + /** + * A callback that will be called when the button is clicked + */ + action: PropTypes.func, + })) + }) +}; + +export default DicomViewer \ No newline at end of file diff --git a/geppetto.js/geppetto-ui/src/dicom-viewer/preconf/README.md b/geppetto.js/geppetto-ui/src/dicom-viewer/preconf/README.md new file mode 100644 index 000000000..76c2482ed --- /dev/null +++ b/geppetto.js/geppetto-ui/src/dicom-viewer/preconf/README.md @@ -0,0 +1,31 @@ +# Preconfigured Dicom Viewer + +Viewer for any images in the DICOM format. + +Features: +* Tiled view with separated orientations at a glance +* Combined view with all orientations +* Single orientation view +* Navigate slice stack for each orientation (mouse wheel) +* Rotate (Left-click + drag) +* Download data (download button) + +```element +dicom-viewer/preconf/DicomViewer +``` + +## Examples + +### Dicom Viewer Example + +The following example shows a brain MRI in DICOM format acquired at The Brain Observatory. +This component is a preconfigured version of the DicomViewer component . +The component provides a default implementation for the buttons. + +``` +dicom-viewer/DicomViewerPreconfExample +``` + +## Libraries + +[AMI.js](https://www.npmjs.com/package/ami.js) diff --git a/geppetto.js/geppetto-ui/src/dicom-viewer/util.js b/geppetto.js/geppetto-ui/src/dicom-viewer/util.js index cf51fbdec..1dad1facc 100644 --- a/geppetto.js/geppetto-ui/src/dicom-viewer/util.js +++ b/geppetto.js/geppetto-ui/src/dicom-viewer/util.js @@ -26,9 +26,9 @@ export function createZipFromRemoteFiles (files, zipName) { // Add an entry to zip per file var zip = new JSZip(); - $.each(files, function (i, filePath) { + for (const filePath of files){ zip.file(filePath.split('/').pop(), urlToPromise(filePath), { binary: true }); - }); + } // Send File zip.generateAsync({ type: "blob" }) diff --git a/geppetto.js/geppetto-ui/tsconfig.json b/geppetto.js/geppetto-ui/tsconfig.json index 64d7d7ee8..14c15e10f 100644 --- a/geppetto.js/geppetto-ui/tsconfig.json +++ b/geppetto.js/geppetto-ui/tsconfig.json @@ -14,6 +14,7 @@ "noImplicitReturns": true, "strict": false, "noUnusedLocals": false, + "allowJs": true, "jsx": "react", "types": [ "jest",