Compare commits
1 Commits
prod-batam
...
coba-exten
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
505462b231 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -59,5 +59,3 @@ tests/playwright-report/
|
||||
|
||||
# Dummy
|
||||
/dump
|
||||
platform/app/dist.zip
|
||||
platform/app/dist-sudirman.zip
|
||||
|
||||
104
add-extensions/expertise-panel/.gitignore
vendored
Normal file
104
add-extensions/expertise-panel/.gitignore
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
11
add-extensions/expertise-panel/.prettierrc
Normal file
11
add-extensions/expertise-panel/.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"proseWrap": "always",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
96
add-extensions/expertise-panel/.webpack/webpack.prod.js
Normal file
96
add-extensions/expertise-panel/.webpack/webpack.prod.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const path = require('path');
|
||||
const pkg = require('../package.json');
|
||||
|
||||
const outputFile = 'index.umd.js';
|
||||
const rootDir = path.resolve(__dirname, '../');
|
||||
const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`);
|
||||
|
||||
// Todo: add ESM build for the extension in addition to umd build
|
||||
|
||||
const config = {
|
||||
mode: 'production',
|
||||
entry: rootDir + '/' + pkg.module,
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
path: outputFolder,
|
||||
filename: outputFile,
|
||||
library: pkg.name,
|
||||
libraryTarget: 'umd',
|
||||
chunkFilename: '[name].chunk.js',
|
||||
umdNamedDefine: true,
|
||||
globalObject: "typeof self !== 'undefined' ? self : this",
|
||||
},
|
||||
externals: [
|
||||
{
|
||||
react: {
|
||||
root: 'React',
|
||||
commonjs2: 'react',
|
||||
commonjs: 'react',
|
||||
amd: 'react',
|
||||
},
|
||||
'@ohif/core': {
|
||||
commonjs2: '@ohif/core',
|
||||
commonjs: '@ohif/core',
|
||||
amd: '@ohif/core',
|
||||
root: '@ohif/core',
|
||||
},
|
||||
'@ohif/ui': {
|
||||
commonjs2: '@ohif/ui',
|
||||
commonjs: '@ohif/ui',
|
||||
amd: '@ohif/ui',
|
||||
root: '@ohif/ui',
|
||||
},
|
||||
},
|
||||
],
|
||||
module: {
|
||||
|
||||
rules: [
|
||||
{
|
||||
test: /\.svg?$/,
|
||||
oneOf: [
|
||||
{
|
||||
use: [
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
svgoConfig: {
|
||||
plugins: [
|
||||
{
|
||||
name: 'preset-default',
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
prettier: false,
|
||||
svgo: true,
|
||||
titleProp: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
issuer: {
|
||||
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /(\.jsx|\.js|\.tsx|\.ts)$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
modules: [path.resolve('./node_modules'), path.resolve('./src')],
|
||||
extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
9
add-extensions/expertise-panel/LICENSE
Normal file
9
add-extensions/expertise-panel/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 expertise-panel ()
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
7
add-extensions/expertise-panel/README.md
Normal file
7
add-extensions/expertise-panel/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# expertise-panel
|
||||
## Description
|
||||
Expertisi Panel extension
|
||||
## Author
|
||||
|
||||
## License
|
||||
MIT
|
||||
50
add-extensions/expertise-panel/babel.config.js
Normal file
50
add-extensions/expertise-panel/babel.config.js
Normal file
@@ -0,0 +1,50 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-class-properties', { loose: true }],
|
||||
'@babel/plugin-transform-typescript',
|
||||
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
|
||||
['@babel/plugin-proposal-private-methods', { loose: true }],
|
||||
],
|
||||
env: {
|
||||
test: {
|
||||
presets: [
|
||||
[
|
||||
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: 'commonjs',
|
||||
debug: false,
|
||||
},
|
||||
],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-regenerator',
|
||||
'@babel/plugin-transform-runtime',
|
||||
'@babel/plugin-transform-typescript',
|
||||
],
|
||||
},
|
||||
production: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
development: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: ['react-refresh/babel'],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
},
|
||||
};
|
||||
75
add-extensions/expertise-panel/package.json
Normal file
75
add-extensions/expertise-panel/package.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "expertise-panel",
|
||||
"version": "0.0.1",
|
||||
"description": "Expertisi Panel extension",
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"main": "dist/umd/expertise-panel/index.umd.js",
|
||||
"files": [
|
||||
"dist/**",
|
||||
"public/**",
|
||||
"README.md"
|
||||
],
|
||||
"repository": "OHIF/Viewers",
|
||||
"keywords": [
|
||||
"ohif-extension"
|
||||
],
|
||||
"module": "src/index.tsx",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:my-extension": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^3.9.1",
|
||||
"@ohif/extension-default": "^3.9.1",
|
||||
"@ohif/extension-cornerstone": "^3.9.1",
|
||||
"@ohif/i18n": "^1.0.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-router": "^6.8.1",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
||||
"@babel/plugin-transform-regenerator": "^7.16.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/plugin-transform-typescript": "^7.13.0",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
|
||||
"babel-eslint": "9.x",
|
||||
"babel-loader": "^8.2.4",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^14.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-loader": "^2.0.0",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-merge": "^5.7.3",
|
||||
"webpack-cli": "^5.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
function ExpertiseSidePanelComponent() {
|
||||
return <div className="w-full text-center text-white">Hello Worlds</div>;
|
||||
}
|
||||
|
||||
export default ExpertiseSidePanelComponent;
|
||||
5
add-extensions/expertise-panel/src/id.js
Normal file
5
add-extensions/expertise-panel/src/id.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const id = packageJson.name;
|
||||
|
||||
export { id };
|
||||
97
add-extensions/expertise-panel/src/index.tsx
Normal file
97
add-extensions/expertise-panel/src/index.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import ExpertiseSidePanelComponent from './ExpertiseSidePanelComponent';
|
||||
import { id } from './id';
|
||||
|
||||
/**
|
||||
* You can remove any of the following modules if you don't need them.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
* You ID can be anything you want, but it should be unique.
|
||||
*/
|
||||
id,
|
||||
|
||||
/**
|
||||
* Perform any pre-registration tasks here. This is called before the extension
|
||||
* is registered. Usually we run tasks such as: configuring the libraries
|
||||
* (e.g. cornerstone, cornerstoneTools, ...) or registering any services that
|
||||
* this extension is providing.
|
||||
*/
|
||||
preRegistration: ({ servicesManager, commandsManager, configuration = {} }) => {},
|
||||
/**
|
||||
* PanelModule should provide a list of panels that will be available in OHIF
|
||||
* for Modes to consume and render. Each panel is defined by a {name,
|
||||
* iconName, iconLabel, label, component} object. Example of a panel module
|
||||
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
|
||||
*/
|
||||
getPanelModule: ({ servicesManager, commandsManager, extensionManager }) => {
|
||||
return [
|
||||
{
|
||||
name: 'ExpertiseSidePanel',
|
||||
iconName: 'logo-ohif-small',
|
||||
iconLabel: 'Expertise Side Panel',
|
||||
label: 'Expertise Side Panel Label',
|
||||
component: ExpertiseSidePanelComponent,
|
||||
},
|
||||
];
|
||||
},
|
||||
/**
|
||||
* ViewportModule should provide a list of viewports that will be available in OHIF
|
||||
* for Modes to consume and use in the viewports. Each viewport is defined by
|
||||
* {name, component} object. Example of a viewport module is the CornerstoneViewport
|
||||
* that is provided by the Cornerstone extension in OHIF.
|
||||
*/
|
||||
getViewportModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
/**
|
||||
* ToolbarModule should provide a list of tool buttons that will be available in OHIF
|
||||
* for Modes to consume and use in the toolbar. Each tool button is defined by
|
||||
* {name, defaultComponent, clickHandler }. Examples include radioGroupIcons and
|
||||
* splitButton toolButton that the default extension is providing.
|
||||
*/
|
||||
getToolbarModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
/**
|
||||
* LayoutTemplateMOdule should provide a list of layout templates that will be
|
||||
* available in OHIF for Modes to consume and use to layout the viewer.
|
||||
* Each layout template is defined by a { name, id, component}. Examples include
|
||||
* the default layout template provided by the default extension which renders
|
||||
* a Header, left and right sidebars, and a viewport section in the middle
|
||||
* of the viewer.
|
||||
*/
|
||||
getLayoutTemplateModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
/**
|
||||
* SopClassHandlerModule should provide a list of sop class handlers that will be
|
||||
* available in OHIF for Modes to consume and use to create displaySets from Series.
|
||||
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
|
||||
* Examples include the default sop class handler provided by the default extension
|
||||
*/
|
||||
getSopClassHandlerModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
/**
|
||||
* HangingProtocolModule should provide a list of hanging protocols that will be
|
||||
* available in OHIF for Modes to use to decide on the structure of the viewports
|
||||
* and also the series that hung in the viewports. Each hanging protocol is defined by
|
||||
* { name, protocols}. Examples include the default hanging protocol provided by
|
||||
* the default extension that shows 2x2 viewports.
|
||||
*/
|
||||
getHangingProtocolModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
/**
|
||||
* CommandsModule should provide a list of commands that will be available in OHIF
|
||||
* for Modes to consume and use in the viewports. Each command is defined by
|
||||
* an object of { actions, definitions, defaultContext } where actions is an
|
||||
* object of functions, definitions is an object of available commands, their
|
||||
* options, and defaultContext is the default context for the command to run against.
|
||||
*/
|
||||
getCommandsModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
/**
|
||||
* ContextModule should provide a list of context that will be available in OHIF
|
||||
* and will be provided to the Modes. A context is a state that is shared OHIF.
|
||||
* Context is defined by an object of { name, context, provider }. Examples include
|
||||
* the measurementTracking context provided by the measurementTracking extension.
|
||||
*/
|
||||
getContextModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
/**
|
||||
* DataSourceModule should provide a list of data sources to be used in OHIF.
|
||||
* DataSources can be used to map the external data formats to the OHIF's
|
||||
* native format. DataSources are defined by an object of { name, type, createDataSource }.
|
||||
*/
|
||||
getDataSourcesModule: ({ servicesManager, commandsManager, extensionManager }) => {},
|
||||
};
|
||||
@@ -26,8 +26,6 @@ const SidePanelWithServices = ({
|
||||
const [sidePanelOpen, setSidePanelOpen] = useState(activeTabIndexProp !== null);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp);
|
||||
const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side));
|
||||
const [studyInstanceUID, setStudyInstanceUID] = useState('');
|
||||
const [lastActivatedStudyUID, setLastActivatedStudyUID] = useState('');
|
||||
|
||||
const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => {
|
||||
setActiveTabIndex(activeTabIndex);
|
||||
@@ -73,33 +71,23 @@ const SidePanelWithServices = ({
|
||||
const activatePanelSubscription = panelService.subscribe(
|
||||
panelService.EVENTS.ACTIVATE_PANEL,
|
||||
(activatePanelEvent: Types.ActivatePanelEvent) => {
|
||||
const isExpertisePanel = activatePanelEvent.panelId.includes('-exp-');
|
||||
// Handle the `-exp` suffix logic
|
||||
const isExpertisePanel = activatePanelEvent.panelId.endsWith('-exp');
|
||||
const realPanelID = isExpertisePanel
|
||||
? activatePanelEvent.panelId.split('-exp-')[0]
|
||||
? activatePanelEvent.panelId.replace(/-exp$/, '')
|
||||
: activatePanelEvent.panelId;
|
||||
|
||||
// studyInstanceUID = take from activatePanelEvent.panelId after '-exp-
|
||||
setStudyInstanceUID(isExpertisePanel ? activatePanelEvent.panelId.split('-exp-')[1] : null);
|
||||
|
||||
const tabIndex = tabs.findIndex(tab => tab.id === realPanelID);
|
||||
|
||||
if (isExpertisePanel && side === 'right') {
|
||||
// Extract study UID from the panel ID
|
||||
const currentStudyUID = activatePanelEvent.panelId.split('-exp-')[1];
|
||||
const shouldOpen = !sidePanelOpen; // Use sidePanelOpen to determine toggle state
|
||||
setSidePanelOpen(shouldOpen);
|
||||
|
||||
// Toggle logic - close if same study is clicked again, open if different study
|
||||
if (currentStudyUID === lastActivatedStudyUID && sidePanelOpen) {
|
||||
// Same study - close panel
|
||||
setSidePanelOpen(false);
|
||||
setActiveTabIndex(null);
|
||||
setLastActivatedStudyUID('');
|
||||
} else {
|
||||
// Different study or panel was closed - open panel with new study
|
||||
setSidePanelOpen(true);
|
||||
setActiveTabIndex(tabIndex !== -1 ? tabIndex : null);
|
||||
setStudyInstanceUID(currentStudyUID);
|
||||
setLastActivatedStudyUID(currentStudyUID);
|
||||
}
|
||||
if (shouldOpen) {
|
||||
setActiveTabIndex(tabIndex !== -1 ? tabIndex : null);
|
||||
} else {
|
||||
setActiveTabIndex(null);
|
||||
}
|
||||
} else if (tabIndex !== -1) {
|
||||
setActiveTabIndex(tabIndex);
|
||||
}
|
||||
@@ -109,7 +97,7 @@ const SidePanelWithServices = ({
|
||||
return () => {
|
||||
activatePanelSubscription.unsubscribe();
|
||||
};
|
||||
}, [tabs, sidePanelOpen, panelService, lastActivatedStudyUID]);
|
||||
}, [tabs, sidePanelOpen, panelService]);
|
||||
|
||||
return (
|
||||
<SidePanel
|
||||
@@ -122,7 +110,6 @@ const SidePanelWithServices = ({
|
||||
onActiveTabIndexChange={handleActiveTabIndexChange}
|
||||
expandedWidth={expandedWidth}
|
||||
servicesManager={servicesManager} // Pass servicesManager ke SidePanel
|
||||
studyInstanceUID={studyInstanceUID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -146,7 +146,6 @@ function ViewerLayout({
|
||||
side="right"
|
||||
activeTabIndex={rightPanelClosedState ? null : 0}
|
||||
servicesManager={servicesManager}
|
||||
expandedWidth={400}
|
||||
/>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
"@ohif/extension-default": "3.9.1",
|
||||
"@ohif/extension-dicom-pdf": "3.9.1",
|
||||
"@ohif/extension-dicom-video": "3.9.1",
|
||||
"@ohif/extension-measurement-tracking": "3.9.1"
|
||||
"@ohif/extension-measurement-tracking": "3.9.1",
|
||||
"expertise-panel": "^0.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
|
||||
@@ -72,6 +72,7 @@ const extensionDependencies = {
|
||||
'@ohif/extension-cornerstone-dicom-rt': '^3.0.0',
|
||||
'@ohif/extension-dicom-pdf': '^3.0.1',
|
||||
'@ohif/extension-dicom-video': '^3.0.1',
|
||||
'expertise-panel': '^0.0.1',
|
||||
};
|
||||
|
||||
function modeFactory({ modeConfiguration }) {
|
||||
@@ -181,7 +182,7 @@ function modeFactory({ modeConfiguration }) {
|
||||
return {
|
||||
id: ohif.layout,
|
||||
props: {
|
||||
leftPanels: [tracked.thumbnailList],
|
||||
leftPanels: [tracked.thumbnailList, 'expertise-panel.panelModule.ExpertiseSidePanel'],
|
||||
rightPanels: [cornerstone.segmentation, tracked.measurements],
|
||||
rightPanelClosed: true,
|
||||
viewports: [
|
||||
|
||||
@@ -78,6 +78,10 @@ module.exports = (env, argv) => {
|
||||
// Hoisted Yarn Workspace Modules
|
||||
path.resolve(__dirname, '../../../node_modules'),
|
||||
SRC_DIR,
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'/home/mario/Works/ohif-viewer/expertise-panel-ext/expertise-panel/node_modules'
|
||||
),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"packageName": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"default": false,
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"packageName": "expertise-panel",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
],
|
||||
"modes": [
|
||||
|
||||
@@ -5,7 +5,6 @@ window.config = {
|
||||
// whiteLabeling: {},
|
||||
extensions: [],
|
||||
modes: [],
|
||||
customizationService: {},
|
||||
showStudyList: true,
|
||||
// some windows systems have issues with more than 3 web workers
|
||||
maxNumberOfWebWorkers: 3,
|
||||
@@ -24,10 +23,21 @@ window.config = {
|
||||
prefetch: 25,
|
||||
},
|
||||
expertise: false, //* Tambahan untuk enable expertise (CustomizableViewportOverlay)
|
||||
expertise_host: `http://192.168.2.13`, // IP ke NV di PACS Server, untuk fetch expertise bawaan versi NV
|
||||
pacs_document_host: `192.168.2.13`, // IP ke NV di PACS Server untuk ambil pdf
|
||||
pacs_document_port: 8080,
|
||||
expertise_host: 'http://128.199.154.150', //* Tambahan untuk fetch data Expertise)
|
||||
|
||||
// filterQueryParam: false,
|
||||
// defaultDataSourceName: 'dicomweb',
|
||||
defaultDataSourceName: 'local-proxy',
|
||||
/* Dynamic config allows user to pass "configUrl" query string this allows to load config without recompiling application. The regex will ensure valid configuration source */
|
||||
// dangerouslyUseDynamicConfig: {
|
||||
// enabled: true,
|
||||
// // regex will ensure valid configuration source and default is /.*/ which matches any character. To use this, setup your own regex to choose a specific source of configuration only.
|
||||
// // Example 1, to allow numbers and letters in an absolute or sub-path only.
|
||||
// // regex: /(0-9A-Za-z.]+)(\/[0-9A-Za-z.]+)*/
|
||||
// // Example 2, to restricts to either hosptial.com or othersite.com.
|
||||
// // regex: /(https:\/\/hospital.com(\/[0-9A-Za-z.]+)*)|(https:\/\/othersite.com(\/[0-9A-Za-z.]+)*)/
|
||||
// regex: /.*/,
|
||||
// },
|
||||
dataSources: [
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
@@ -35,8 +45,9 @@ window.config = {
|
||||
configuration: {
|
||||
friendlyName: 'Static WADO Local Data',
|
||||
name: 'DCM4CHEE',
|
||||
qidoRoot: `http://192.168.2.13:5000/rs`, // IP ke dicomweb-proxy PACS Server. URI selalu /rs
|
||||
wadoRoot: `http://192.168.2.13:5000/rs`, // IP ke dicomweb-proxy PACS Server. URI selalu /rs qidoSupportsIncludeField: false,
|
||||
qidoRoot: 'http://128.199.154.150:5000/rs',
|
||||
wadoRoot: 'http://128.199.154.150:5000/rs',
|
||||
qidoSupportsIncludeField: false,
|
||||
supportsReject: true,
|
||||
supportsStow: true,
|
||||
imageRendering: 'wadors',
|
||||
@@ -52,7 +63,6 @@ window.config = {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomwebproxy',
|
||||
sourceName: 'dicomwebproxy',
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/** @type {AppTypes.Config} */
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
pacs_document_host: '152.42.173.210',
|
||||
pacs_document_port: 8080,
|
||||
expertise: false,
|
||||
enableGoogleCloudAdapter: false,
|
||||
// below flag is for performance reasons, but it might not work for all servers
|
||||
showWarningMessageForCrossOrigin: true,
|
||||
@@ -11,24 +8,37 @@ window.config = {
|
||||
showLoadingIndicator: true,
|
||||
strictZSpacingForVolumeViewport: true,
|
||||
// This is an array, but we'll only use the first entry for now
|
||||
// Remove OIDC configuration since proxy handles authentication
|
||||
// oidc: [
|
||||
// {
|
||||
// // ~ REQUIRED
|
||||
// // Authorization Server URL
|
||||
// authority: 'https://accounts.google.com',
|
||||
// client_id: '382212153306-7q39hdie4ecj0uhemkitvedo93bnvfhn.apps.googleusercontent.com',
|
||||
// redirect_uri: '/callback',
|
||||
// response_type: 'id_token token',
|
||||
// scope:
|
||||
// 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
|
||||
// // ~ OPTIONAL
|
||||
// post_logout_redirect_uri: '/logout-redirect.html',
|
||||
// revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
|
||||
// automaticSilentRenew: true,
|
||||
// revokeAccessTokenOnSignout: true,
|
||||
// },
|
||||
// ],
|
||||
oidc: [
|
||||
{
|
||||
// ~ REQUIRED
|
||||
// Authorization Server URL
|
||||
authority: 'https://accounts.google.com',
|
||||
client_id: '382212153306-7q39hdie4ecj0uhemkitvedo93bnvfhn.apps.googleusercontent.com',
|
||||
redirect_uri: '/callback',
|
||||
response_type: 'id_token token',
|
||||
scope:
|
||||
'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
|
||||
// ~ OPTIONAL
|
||||
post_logout_redirect_uri: '/logout-redirect.html',
|
||||
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
|
||||
automaticSilentRenew: true,
|
||||
revokeAccessTokenOnSignout: true,
|
||||
|
||||
// Tambahan dari Google CLoud Secret
|
||||
project_id: "westone-433204",
|
||||
auth_uri: "https://accounts.google.com/o/oauth2/auth",
|
||||
token_uri: "https://oauth2.googleapis.com/token",
|
||||
auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
|
||||
client_secret: "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",
|
||||
redirect_uris: [
|
||||
"http://devkedungdoro.aplikasi.web.id:3000/callback"
|
||||
],
|
||||
javascript_origins: [
|
||||
"https://devone.aplikasi.web.id",
|
||||
"http://devkedungdoro.aplikasi.web.id:3000"
|
||||
]
|
||||
},
|
||||
],
|
||||
extensions: [],
|
||||
modes: [],
|
||||
showStudyList: true,
|
||||
@@ -41,9 +51,12 @@ window.config = {
|
||||
configuration: {
|
||||
friendlyName: 'dcmjs DICOMWeb Server',
|
||||
name: 'GCP',
|
||||
wadoUriRoot: `http://152.42.173.210:5555/dicomWeb`,
|
||||
qidoRoot: `http://152.42.173.210:5555/dicomWeb`,
|
||||
wadoRoot: `http://152.42.173.210:5555/dicomWeb`,
|
||||
wadoUriRoot:
|
||||
'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif/dicomWeb',
|
||||
qidoRoot:
|
||||
'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif/dicomWeb',
|
||||
wadoRoot:
|
||||
'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif/dicomWeb',
|
||||
qidoSupportsIncludeField: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
@@ -53,12 +66,12 @@ window.config = {
|
||||
dicomUploadEnabled: true,
|
||||
omitQuotationForMultipartRequest: true,
|
||||
configurationAPI: 'ohif.dataSourceConfigurationAPI.google',
|
||||
// defaultDicomStoreConfiguredItems: {
|
||||
// id: 'projects/ohifproxy/locations/asia-southeast2/datasets/sas-storage',
|
||||
// itemType: '3',
|
||||
// name: 'store-1',
|
||||
// url: 'https://healthcare.googleapis.com/v1/projects/ohifproxy/locations/asia-southeast2/datasets/sas-storage/dicomStores/store-1'
|
||||
// },
|
||||
defaultDicomStoreConfiguredItems: {
|
||||
id: 'projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage',
|
||||
itemType: '3',
|
||||
name: 'ohif',
|
||||
url: 'https://healthcare.googleapis.com/v1/projects/westone-433204/locations/asia-southeast2/datasets/sas-dicom-storage/dicomStores/ohif'
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -154,7 +154,6 @@ const SidePanel = ({
|
||||
expandedWidth = 280,
|
||||
onActiveTabIndexChange,
|
||||
servicesManager, // Tambah servicesManager as a prop
|
||||
studyInstanceUID,
|
||||
}) => {
|
||||
const [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(0);
|
||||
@@ -167,8 +166,8 @@ const SidePanel = ({
|
||||
const [viewportData, setViewportData] = useState(null);
|
||||
|
||||
// Harusnya (viewportId), tapi karena gabutuh perubahan viewport maka dihardcode 'default'
|
||||
// const viewportInfo = cornerstoneViewportService.getViewportInfo('default');
|
||||
// const studyInstanceUID = viewportInfo?.viewportData?.data?.[0]?.StudyInstanceUID || '';
|
||||
const viewportInfo = cornerstoneViewportService.getViewportInfo('default');
|
||||
const studyInstanceUID = viewportInfo?.viewportData?.data?.[0]?.StudyInstanceUID || '';
|
||||
|
||||
const styleMap = createStyleMap(expandedWidth, borderSize, collapsedWidth);
|
||||
const baseStyle = createBaseStyle(expandedWidth);
|
||||
@@ -360,7 +359,46 @@ const SidePanel = ({
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
console.log('Study data:', data);
|
||||
// console.log('Study data:', data);
|
||||
|
||||
const data = {
|
||||
study: {
|
||||
accession_no: 'CR.250411.001',
|
||||
study_iuid: '1.2.826.0.1.3680043.9.7307.1.20250411001',
|
||||
study_description: '',
|
||||
study_datetime: '20250411093937',
|
||||
number_of_series: '1',
|
||||
number_of_instances: '1',
|
||||
modality: 'CR',
|
||||
patient_mrn: '00000941',
|
||||
patient_name: 'NEFANNY RIDWAN',
|
||||
patient_sex: 'F',
|
||||
patient_date_of_birth: '19881127',
|
||||
patient_age: '36Y 4M 14D',
|
||||
expertise: [
|
||||
{
|
||||
expertise:
|
||||
'Keterangan : MCU\r\n\r\nRadiografi Thorax PA (inspirasi kurang)\r\n\r\nCor : besar dan bentuk normal\r\nPulmo : tak tampak infiltrat\r\nTrachea tampak di tengah\r\nSinus phrenicocostalis kanan kiri tajam\r\nHemidiafragma kanan kiri tampak baik\r\nTulang-tulang tampak baik\r\nSoft tissue tak tampak kelainan\r\n\r\nKesan :\r\nTidak tampak kelainan signifikan pada pemeriksaan saat ini\r\n\r\nBTK,',
|
||||
radiologist: 'dr. Hendra Boy Situmorang, Sp.Rad ',
|
||||
expertise_dttm: '2025-04-11 09:43',
|
||||
radiologist_edit: null,
|
||||
expertise_edit_dttm: '0000-00-00 00:00',
|
||||
ordering_physician: 'dr. Laksmitasari Dewi',
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
series_number: '1',
|
||||
series_iuid: '1.2.156.112536.2.560.28134011043131122.1519098341436.1',
|
||||
series_description: 'V04_0014',
|
||||
number_of_instances: 1,
|
||||
thumbnail:
|
||||
'http://192.168.22.3/nv/wado_proxy_thumb.php?requestType=WADO&studyUID=1.2.826.0.1.3680043.9.7307.1.20250411001&seriesUID=1.2.156.112536.2.560.28134011043131122.1519098341436.1&objectUID=1.2.156.112536.2.560.28134011043131122.1519098341436.4&rows=123',
|
||||
sop_iuids: ['1.2.156.112536.2.560.28134011043131122.1519098341436.4'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if (data?.study?.expertise && data.study.expertise.length > 0) {
|
||||
setExpertiseData(data.study.expertise[0]);
|
||||
@@ -441,7 +479,7 @@ const SidePanel = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<ScrollArea className="border-input bg-background h-[500px] w-full rounded-md border p-2 text-sm text-white">
|
||||
<ScrollArea className="border-input bg-background h-[500px] w-[350px] rounded-md border p-2 text-sm text-white">
|
||||
<h3 className="mb-4 text-lg font-bold">Expertise</h3>
|
||||
{formattedData.map((section, index) => (
|
||||
<div
|
||||
@@ -452,16 +490,11 @@ const SidePanel = ({
|
||||
{Array.isArray(section.value) ? (
|
||||
<ul className="list-disc pl-6">
|
||||
{section.value.map((item, idx) => (
|
||||
<li
|
||||
key={idx}
|
||||
className="break-words"
|
||||
>
|
||||
{item}
|
||||
</li>
|
||||
<li key={idx}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="break-words">{section.value}</p>
|
||||
<p>{section.value}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
@@ -629,7 +662,6 @@ SidePanel.propTypes = {
|
||||
onActiveTabIndexChange: PropTypes.func,
|
||||
expandedWidth: PropTypes.number,
|
||||
servicesManager: PropTypes.object.isRequired, // Tambah servicesManager prop
|
||||
studyInstanceUID: PropTypes.string, // Tambahkan prop studyInstanceUID
|
||||
};
|
||||
|
||||
export { SidePanel };
|
||||
|
||||
@@ -63,7 +63,6 @@ const StudyBrowser = ({
|
||||
viewPreset={viewPreset}
|
||||
onThumbnailContextMenu={onThumbnailContextMenu}
|
||||
servicesManager={servicesManager} // Pass servicesManager ke Study Item
|
||||
studyInstanceUid={studyInstanceUid}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -21,10 +21,7 @@ const StudyItem = ({
|
||||
viewPreset = 'thumbnails',
|
||||
onThumbnailContextMenu,
|
||||
servicesManager, // Tambah servicesManager as a prop
|
||||
studyInstanceUid = '',
|
||||
}: withAppTypes) => {
|
||||
// FETCHING ACCESSION NUMBER DAN EXPERTISE
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
type="single"
|
||||
@@ -62,17 +59,16 @@ const StudyItem = ({
|
||||
<>
|
||||
{/* Expertise Button */}
|
||||
<div
|
||||
className="bg-primary-dark hover:bg-primary-active mx-8 my-4 cursor-pointer rounded-lg border border-white py-3 text-center text-white"
|
||||
className="bg-primary-dark hover:bg-primary-active my-4 w-full cursor-pointer border border-white py-2 text-center text-white"
|
||||
onClick={() => {
|
||||
// Trigger the expertise panel in the right side panel (segmentation Panel)
|
||||
servicesManager.services.panelService.activatePanel(
|
||||
// '@ohif/extension-cornerstone.panelModule.panelSegmentation-exp',
|
||||
`@ohif/extension-cornerstone.panelModule.panelSegmentation-exp-${studyInstanceUid}`,
|
||||
'@ohif/extension-cornerstone.panelModule.panelSegmentation-exp',
|
||||
true
|
||||
);
|
||||
}}
|
||||
>
|
||||
Expertise
|
||||
View Expertise
|
||||
</div>
|
||||
|
||||
{/* Thumbnails */}
|
||||
@@ -109,7 +105,6 @@ StudyItem.propTypes = {
|
||||
onClickUntrack: PropTypes.func,
|
||||
viewPreset: PropTypes.string,
|
||||
servicesManager: PropTypes.object.isRequired, // Tambah servicesManager prop
|
||||
studyInstanceUid: PropTypes.string.string,
|
||||
};
|
||||
|
||||
export { StudyItem };
|
||||
|
||||
Reference in New Issue
Block a user