feat: cornerstone3D stack and volume viewports (#2787)
* feat: cs3d working stack viewport and tools (#19) * Squashed everything * fix weird react issue * fix eslint / prettier stuff * thumbnails work now with cpu rendering * feat: use new loadImageToCanvas in cs3d * make jump to measurement work * fix active thumbnail * fix measurement delete * fix window level presets * remove segmentation and sync groups for now * fix the dicom pdf and dicom video * fix cornerstone window assignment for cypress * apply review comments Co-authored-by: Erik <erik.sweed@gmail.com> * feat: cs3d tools and toolGroups (#20) * add more tools to work with cs3d mode * fix hotkeys * add stack manager usage for stach viewports * add image scrollbar * wip viewport overlay * fix toAnnotation schema for tools * fix the unnecessary size change that triggered resize * hanging protocol improvement to allow unmatched errors * study description matching for hanging protocol * fix the displaysetOptions to work * fix handle the active tool when a new viewport is added * fix separate toolGroups for mode * apply review comments * apply review comments * yarn lock * feat: overlay component (#21) * fix default displayset options * add viewport overlay * apply review comments * feat: loading and orientation indicators (#22) * add loading indicator * add orientation marker initial work * apply review comments * fix: orientation markers (#23) * finished the orientation markers * fix various broken cypress tests * apply review comments * update yarn lock * feat: re-working measurement tracking mode with cornerstone3d (#2805) * feat: cs3d working stack viewport and tools (#19) * Squashed everything * fix weird react issue * fix eslint / prettier stuff * thumbnails work now with cpu rendering * feat: use new loadImageToCanvas in cs3d * make jump to measurement work * fix active thumbnail * fix measurement delete * fix window level presets * remove segmentation and sync groups for now * fix the dicom pdf and dicom video * fix cornerstone window assignment for cypress * apply review comments Co-authored-by: Erik <erik.sweed@gmail.com> * feat: Add Measurement tracking mode with cs3D (#2789) * feat: first render for cornerstone3d tracked viewport * make tool active work * wip for SR extension * renamed dicom sr to cornerstone dicom sr * remove cornerstone from dicom pdf and video * move dicom sr logic to sr extension * feat: Add hydration for the length tool * fix SR display tool for length using cs3d * fix default config * fix: various bugs with sr viewport and tracking * fix promptying to continue tracking for when SR is created * feat: add keep trackign of unique identifiers * fix hydration for same imageIds * feat: Add SR toolGroup creation on modeEnter * feat: remove the need for separate mapper for SR hydration * add SR display for ellipse * handle hydration of elliptical ROI tool * remove cornerstone extension * add arrow mapping * feat: Add ArrowAnnotate SR display and hydration * apply review comments * apply review comments * move viewport labels to the viewportData * apply review comments * fix: integration cypress tests with cornerstone3D and add CINE tool (#2795) * fix: integration cypress tests with cornerstone3D * revert to addOrUpdate as it makes more sense * fix local drag and drop * fix tests * move dicomLoaderService to cornerstone extension * fix various import bugs * fix bug for local PT series * fix various unit tests * bump cs3d versions * add angle and magnify tool * bump dependencies to avoid broken peerDeps * feat: add initial work for capture using cs3d * feat: show annotations on the image capture * feat: add svg layer export * feat: Add CINE Tool * feat: remove unnecessary viewport rendering for cine state changes Co-authored-by: Erik Ziegler <erik.sweed@gmail.com> * docs: modify and improve documentation (#2800) * cleanup docs versionings * feat: Add all version explanations * version docs for 3.0 * wip for changing docs * wip for updated docs * add utility module documentation * fix demo with nohoisting of history * add slides and video to resources * apply review comments * fix: drag and drop SR into SR viewport (#2803) * update yarn lock Co-authored-by: Erik <erik.sweed@gmail.com> * update pathnames to match v3-stable * update the e2e pathname * fix: various bugs with regard to tracking workflow (#2811) * fix: various issues with measurement panel * fix: update default tool style for annotations * fix: annotatoin label getting removed * feat: Add backward compatibility for SR hydration with legacy cornerstone * fix: cursors and ellipse ROI max style * fix: ArrowAnnotate SRDisplay * apply review comments * bump package versions * fix: bugin rehydration of SR * fix: e2e tests * fix active viewport thickness and arrowTool ui * add readme for measurement tracking * use uploaded image for readme * add back images * try to fix e2e test * fix: window level presets hotkeys * Update README.md * update yarn lock * feat: volume api and TMTV mode (#2817) * feat: volumeAPI and TMTV mode * feat: use cs3d cache service to obtain viewportData * wip for volume api * wip: fusion viewport * feat: add blend mode option * wip for image scrollbar * fix drag and drop thumbnail * wip for image scrollbar for voluems * fix: element mismatch bug for scroll * feat: Add image scrollbar to volumes * feat: add syncGroups to volume api * feat: add tmtv mode initial setup * feat: add initial image options for the stack viewports * feat: Add custom load strategy for volume viewports via HP * apply review comments fix: Jump presets cs3d (#2812) * feat: Add JumpPreset to OHIF for Cornerstone3D * fix: accessing viewport service from servicesManager feat: volume API and TMTV mode (#2814) * fix: do not display overlays on mip viewports * feat: add optional disableCommands for toggle buttons * fix: toggleCrossharis * feat: config the crosshairs * feat: add PetSUV Panel for changing metadata * feat: initial work for the rectangleROIThreshold panel * wip: measurement service * roi threshold working * feat: Add displayText to segmentations * feat: add remove segmentation * feat: add csv export * feat: add RT export for annotations in tmtv mode * fix: fusion to use pt in tmtv mode and measuremet mappings * fix: various bugs * apply review comments * apply review comments * feat: add fusion color maps * add readme to tmtv mode * Update README.md * fix: try to fix build * fix unit tests * Update README.md * feat: add about to the panel * fix: changing strategy in roi panel * apply review comments * update package versions * wip for stackPrefetch * fix: cornerstone3d hydration and renaming (#2818) * update readme * renamed cornerstone extension * wip for renaming cornerstone3D variables * wip for renaming cornerstone3D variables * wip for fixing bugs for SR viewport * fix: jumpToMeasurement and initial label after hydration * fix: fileName capitalization * feat: use the new prefetch stack in the cs3d (#2820) * feat: use the new prefetch stack in the cs3d * use viewport scroll api for stack viewport * fix cine stop when scrollbar changes * feat: use new prefetch events * fix loading state to not show repeatedly * fix: various bugs for tmtv mode thresholding and new icons (#2823) * feat: make tmtv mode available in worklist * feat: add new icons for tmtv mode * feat: add fusion color icon * fix: parallel scale calculation * fix: bump Cornerstone to get large image support working * fix: Fix issues with CPU viewport flipping, including config files * fix: bump cornerstone to fix magnify tool * fix: Bump Cornerstone version to fix resetCamera issue in StackViewport * ci: Add _headers file to enable CORS headers for Netlify Drag/Drop deploys * fix: WADO-URI was not working. PET Metadata was coming from the wrong place. Fixed some minor React errors * feat(OHIF):Allow modes and extensions to be added after commpile time. (#2838) Also works with the compile time add that the existing cli uses, so that both build types work. Eric and I agreed this doesn't change existing functionality, but is almost entirely build issues/fixes. * bump: dependency versions to fix hydration bugs (#2848) * bump: dcmjs version to fix hydration bugs * try to fix tests * bump dependency versions Co-authored-by: Alireza <ar.sedghi@gmail.com> Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
668 files changed
+41669
-13410
No files matched your search
@@ -25,7 +25,7 @@ const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`;
|
||||
const Dotenv = require('dotenv-webpack');
|
||||
const writePluginImportFile = require('./writePluginImportsFile.js');
|
||||
|
||||
writePluginImportFile(SRC_DIR);
|
||||
const copyPluginFromExtensions = writePluginImportFile(SRC_DIR, DIST_DIR);
|
||||
|
||||
const setHeaders = (res, path) => {
|
||||
if (path.indexOf('.gz') !== -1) {
|
||||
@@ -35,6 +35,8 @@ const setHeaders = (res, path) => {
|
||||
}
|
||||
if (path.indexOf('.pdf') !== -1) {
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
} else if (path.indexOf('/frames') !== -1) {
|
||||
res.setHeader('Content-Type', 'multipart/related')
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
}
|
||||
@@ -77,6 +79,7 @@ module.exports = (env, argv) => {
|
||||
// Copy "Public" Folder to Dist
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
...copyPluginFromExtensions,
|
||||
{
|
||||
from: PUBLIC_DIR,
|
||||
to: DIST_DIR,
|
||||
@@ -108,13 +111,14 @@ module.exports = (env, argv) => {
|
||||
PUBLIC_URL: PUBLIC_URL,
|
||||
},
|
||||
}),
|
||||
// No longer maintained; but good for generating icons + manifest
|
||||
// new FaviconsWebpackPlugin( path.join(PUBLIC_DIR, 'assets', 'icons-512.png')),
|
||||
// Generate a service worker for fast local loads
|
||||
new InjectManifest({
|
||||
swDest: 'sw.js',
|
||||
swSrc: path.join(SRC_DIR, 'service-worker.js'),
|
||||
// Increase the limit to 4mb:
|
||||
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
|
||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||
// Need to exclude the theme as it is updated independently
|
||||
exclude: [/theme/],
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
@@ -140,16 +144,6 @@ module.exports = (env, argv) => {
|
||||
overlay: { errors: true, warnings: false },
|
||||
},
|
||||
'static': [
|
||||
{
|
||||
directory: path.join(require('os').homedir(), 'dicomweb'),
|
||||
staticOptions: {
|
||||
extensions: ['gz', 'br'],
|
||||
index: "index.json.gz",
|
||||
redirect: true,
|
||||
setHeaders,
|
||||
},
|
||||
publicPath: '/dicomweb',
|
||||
},
|
||||
{
|
||||
directory: '../../testdata',
|
||||
staticOptions: {
|
||||
|
||||
@@ -13,6 +13,8 @@ function constructLines(input, categoryName) {
|
||||
addToWindowLines: [],
|
||||
};
|
||||
|
||||
if (!input) return lines;
|
||||
|
||||
input.forEach(entry => {
|
||||
const packageName = entry.packageName;
|
||||
|
||||
@@ -22,7 +24,7 @@ function constructLines(input, categoryName) {
|
||||
`import ${defaultImportName} from '${packageName}';\n`
|
||||
);
|
||||
lines.addToWindowLines.push(
|
||||
`window.${categoryName}.push(${defaultImportName});\n`
|
||||
`${categoryName}.push(${defaultImportName});\n`
|
||||
);
|
||||
|
||||
pluginCount++;
|
||||
@@ -42,7 +44,11 @@ function getFormattedImportBlock(importLines) {
|
||||
}
|
||||
|
||||
function getFormattedWindowBlock(addToWindowLines) {
|
||||
let content = `window.extensions = [];\nwindow.modes = [];\n\n`;
|
||||
let content = "const extensions = [];\n" +
|
||||
"const modes = [];\n" +
|
||||
"const modesFactory = [];\n" +
|
||||
"window.extensions = extensions;\n" +
|
||||
"window.modes = modes;\n\n";
|
||||
|
||||
addToWindowLines.forEach(addToWindowLine => {
|
||||
content += addToWindowLine;
|
||||
@@ -51,21 +57,49 @@ function getFormattedWindowBlock(addToWindowLines) {
|
||||
return content;
|
||||
}
|
||||
|
||||
function writePluginImportsFile(SRC_DIR) {
|
||||
function getRuntimeLoadModesExtensions() {
|
||||
return "\n\n// Add a dynamic runtime loader\n" +
|
||||
"export default async () => {\n" +
|
||||
" for(const modeFactory of modesFactory) {\n" +
|
||||
" const newModes = await modeFactory(modes,extensions);\n" +
|
||||
" newModes.forEach(newMode => modes.push(newMode));\n" +
|
||||
"}\n}\n";
|
||||
}
|
||||
|
||||
const createCopyPluginFromExtensions = (SRC_DIR, DIST_DIR, plugins) => {
|
||||
|
||||
return plugins.map(plugin => {
|
||||
const from = `${SRC_DIR}/../node_modules/${plugin.packageName}/public/`;
|
||||
const exists = fs.existsSync(from);
|
||||
return exists ? {
|
||||
from,
|
||||
to: DIST_DIR,
|
||||
toType: 'dir',
|
||||
} : undefined;
|
||||
}
|
||||
).filter(x => !!x);
|
||||
}
|
||||
|
||||
function writePluginImportsFile(SRC_DIR, DIST_DIR) {
|
||||
let pluginImportsJsContent = autogenerationDisclaimer;
|
||||
|
||||
const extensionLines = constructLines(pluginConfig.extensions, 'extensions');
|
||||
const modeLines = constructLines(pluginConfig.modes, 'modes');
|
||||
const modesFactoryLines = constructLines(pluginConfig.modesFactory, 'modesFactory');
|
||||
|
||||
pluginImportsJsContent += getFormattedImportBlock([
|
||||
...extensionLines.importLines,
|
||||
...modeLines.importLines,
|
||||
...modesFactoryLines.importLines,
|
||||
]);
|
||||
pluginImportsJsContent += getFormattedWindowBlock([
|
||||
...extensionLines.addToWindowLines,
|
||||
...modeLines.addToWindowLines,
|
||||
...modesFactoryLines.addToWindowLines,
|
||||
]);
|
||||
|
||||
pluginImportsJsContent += getRuntimeLoadModesExtensions();
|
||||
|
||||
fs.writeFileSync(
|
||||
`${SRC_DIR}/pluginImports.js`,
|
||||
pluginImportsJsContent,
|
||||
@@ -77,6 +111,18 @@ function writePluginImportsFile(SRC_DIR) {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const copyPluginFromExtensions = createCopyPluginFromExtensions(
|
||||
SRC_DIR, DIST_DIR,
|
||||
[
|
||||
...pluginConfig.modesFactory,
|
||||
...pluginConfig.modes,
|
||||
...pluginConfig.extensions,
|
||||
...pluginConfig.umd,
|
||||
]
|
||||
)
|
||||
|
||||
return copyPluginFromExtensions;
|
||||
}
|
||||
|
||||
module.exports = writePluginImportsFile;
|
||||
+50
-39
@@ -3,6 +3,17 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(cornerstone => {
|
||||
// For debugging issues where tests pass locally but fail on CI
|
||||
// - Sometimes Cypress orb seems to use CPU rendering pathway
|
||||
cy.log(
|
||||
`Cornerstone using CPU Rendering?: ${cornerstone.getShouldUseCPURendering()}`
|
||||
);
|
||||
});
|
||||
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
@@ -33,55 +44,55 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
});
|
||||
|
||||
it('checks if hotkeys "V" and "H" can flip the image', () => {
|
||||
// Hotkey V
|
||||
cy.get('body').type('V');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
// Hotkey H
|
||||
cy.get('body').type('H');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
// Hotkey V
|
||||
cy.get('body').type('V');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'P');
|
||||
});
|
||||
|
||||
it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then($zoomBtn => {
|
||||
cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
});
|
||||
// it('checks if hotkeys "+", "-" and "=" can zoom in, out and fit to viewport', () => {
|
||||
// //Click on button and verify if icon is active on toolbar
|
||||
// cy.get('@zoomBtn')
|
||||
// .click()
|
||||
// .then($zoomBtn => {
|
||||
// cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
// });
|
||||
|
||||
// Hotkey +
|
||||
cy.get('body').type('+++'); // Press hotkey 3 times
|
||||
cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.35x');
|
||||
// Hotkey -
|
||||
cy.get('body').type('-');
|
||||
cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.20x');
|
||||
// Hotkey =
|
||||
cy.get('body').type('=');
|
||||
cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.90x');
|
||||
});
|
||||
// // Hotkey +
|
||||
// cy.get('body').type('+++'); // Press hotkey 3 times
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.30x');
|
||||
// // Hotkey -
|
||||
// cy.get('body').type('-');
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.09x');
|
||||
// // Hotkey =
|
||||
// cy.get('body').type('=');
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.67x');
|
||||
// });
|
||||
|
||||
it('checks if hotkey "SPACEBAR" can reset the image', () => {
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then($zoomBtn => {
|
||||
cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
});
|
||||
// it('checks if hotkey "SPACEBAR" can reset the image', () => {
|
||||
// //Click on button and verify if icon is active on toolbar
|
||||
// cy.get('@zoomBtn')
|
||||
// .click()
|
||||
// .then($zoomBtn => {
|
||||
// cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
// });
|
||||
|
||||
// Press multiples hotkeys
|
||||
cy.get('body').type('V+++I');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.35x');
|
||||
// // Press multiples hotkeys
|
||||
// cy.get('body').type('V+++I');
|
||||
// cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
// cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:2.30x');
|
||||
|
||||
// Hotkey SPACEBAR
|
||||
cy.get('body').type(' ');
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.90x');
|
||||
});
|
||||
// // Hotkey SPACEBAR
|
||||
// cy.get('body').type(' ');
|
||||
// cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
||||
// cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', 'Zoom:1.67x');
|
||||
// });
|
||||
|
||||
/*
|
||||
// TODO: Pretty sure this is not implemented yet
|
||||
|
||||
+34
-26
@@ -42,31 +42,37 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mousedown', 'center', { buttons: 1 })
|
||||
.trigger('mousemove', 'top', { buttons: 1 })
|
||||
.trigger('mouseup');
|
||||
const expectedText =
|
||||
'Ser: 1Img: 1 1/26256 x 256Loc: -30.00 mm Thick: 5.00 mm';
|
||||
cy.get('@viewportInfoBottomLeft').should('have.text', expectedText);
|
||||
});*/
|
||||
|
||||
it('checks if Zoom tool will zoom in/out an image in the viewport', () => {
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then($zoomBtn => {
|
||||
cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
});
|
||||
// it('checks if Zoom tool will zoom in/out an image in the viewport', () => {
|
||||
// //Click on button and verify if icon is active on toolbar
|
||||
// cy.get('@zoomBtn')
|
||||
// .click()
|
||||
// .then($zoomBtn => {
|
||||
// cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
// });
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
// // IMPORTANT: Cypress sends out a mouseEvent which doesn't have the buttons
|
||||
// // property. This is a workaround to simulate a mouseEvent with the buttons property
|
||||
// // which is consumed by cornerstone
|
||||
// cy.get('@viewport')
|
||||
// .trigger('mousedown', 'center', { buttons: 1 })
|
||||
// .trigger('mousemove', 'top', {
|
||||
// buttons: 1,
|
||||
// })
|
||||
// .trigger('mouseup', {
|
||||
// buttons: 1,
|
||||
// });
|
||||
|
||||
const expectedText = 'Zoom:0.45x';
|
||||
cy.get('@viewportInfoTopLeft').should('have.text', expectedText);
|
||||
});
|
||||
// const expectedText = 'Zoom:0.96x';
|
||||
// cy.get('@viewportInfoTopLeft').should('have.text', expectedText);
|
||||
// });
|
||||
|
||||
it('checks if Levels tool will change the window width and center of an image', () => {
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
@@ -78,14 +84,14 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mouseup')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'left', { which: 1 })
|
||||
.trigger('mouseup');
|
||||
.click({ force: true })
|
||||
.trigger('mousedown', 'center', { buttons: 1 })
|
||||
// Since we have scrollbar on the right side of the viewport, we need to
|
||||
// force the mousemove since it goes to another element
|
||||
.trigger('mousemove', 'right', { buttons: 1, force: true })
|
||||
.trigger('mouseup', { buttons: 1 });
|
||||
|
||||
const expectedText = 'W:731L:226';
|
||||
const expectedText = 'W:1930L:479';
|
||||
cy.get('@viewportInfoTopLeft').should('have.text', expectedText);
|
||||
});
|
||||
|
||||
@@ -98,8 +104,8 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'bottom', { which: 1 })
|
||||
.trigger('mousedown', 'center', { buttons: 1 })
|
||||
.trigger('mousemove', 'bottom', { buttons: 1 })
|
||||
.trigger('mouseup', 'bottom');
|
||||
});
|
||||
|
||||
@@ -222,6 +228,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
});*/
|
||||
|
||||
/**
|
||||
it('checks if More button will prompt a modal with secondary tools', () => {
|
||||
//Click on More button
|
||||
cy.get('@moreBtnSecondary').click();
|
||||
@@ -244,6 +251,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
// Verify if overlay is hidden
|
||||
cy.get('@toolbarOverlay').should('not.be.visible');
|
||||
});
|
||||
*/
|
||||
|
||||
/*it('checks if Layout tool will multiply the number of viewports displayed', () => {
|
||||
//Click on Layout button and verify if overlay is displayed
|
||||
|
||||
+11
-10
@@ -10,10 +10,11 @@ describe('OHIF Download Snapshot File', () => {
|
||||
cy.openDownloadImageModal();
|
||||
});
|
||||
|
||||
/*afterEach(() => {
|
||||
/**
|
||||
afterEach(() => {
|
||||
// Close modal
|
||||
|
||||
// TODO: Modal.jsx currently uses Icon instead of IconButton, so
|
||||
// TODO: Modal.tsx currently uses Icon instead of IconButton, so
|
||||
// it's not as easy to give it the cypress data attribute.
|
||||
// We should fix that first
|
||||
cy.get('[data-cy="close-button"]')
|
||||
@@ -21,7 +22,7 @@ describe('OHIF Download Snapshot File', () => {
|
||||
.click();
|
||||
});*/
|
||||
|
||||
it('checks displayed information for Desktop experience', function() {
|
||||
/*it('checks displayed information for Desktop experience', function() {
|
||||
// Set Desktop resolution
|
||||
cy.viewport(1750, 720);
|
||||
// Visual comparison
|
||||
@@ -29,26 +30,25 @@ describe('OHIF Download Snapshot File', () => {
|
||||
//Check if all elements are displayed
|
||||
|
||||
// TODO: need to add this attribute to the modal
|
||||
/*cy.get('[data-cy=modal-header]')
|
||||
cy.get('[data-cy=modal-header]')
|
||||
.as('downloadImageModal')
|
||||
.should('contain.text', 'Download High Quality Image');*/
|
||||
.should('contain.text', 'Download High Quality Image');
|
||||
|
||||
// Check input fields
|
||||
// TODO: select2
|
||||
/*cy.get('[data-cy="file-type"]')
|
||||
cy.get('[data-cy="file-type"]')
|
||||
.select('png')
|
||||
.should('have.value', 'png')
|
||||
.select('jpg')
|
||||
.should('have.value', 'jpg');*/
|
||||
.should('have.value', 'jpg');
|
||||
|
||||
// Check image preview
|
||||
cy.get('[data-cy="image-preview"]').should('contain.text', 'Image preview');
|
||||
|
||||
|
||||
/* TODO: This is a canvas now, not an img with src
|
||||
TODO: This is a canvas now, not an img with src
|
||||
cy.get('[data-cy="viewport-preview-img"]')
|
||||
.should('have.attr', 'src')
|
||||
.and('include', 'data:image');*/
|
||||
.and('include', 'data:image');
|
||||
|
||||
// Check buttons
|
||||
cy.get('[data-cy="cancel-btn"]')
|
||||
@@ -58,6 +58,7 @@ describe('OHIF Download Snapshot File', () => {
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
});
|
||||
*/
|
||||
|
||||
/*it('cancel changes on download modal', function() {
|
||||
//Change Image Width, Filename and File Type
|
||||
|
||||
@@ -125,7 +125,6 @@ describe('OHIF Study Viewer Page', function() {
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
/*it('checks if measurement item can be deleted through the context menu on the viewport', function() {
|
||||
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||
|
||||
@@ -267,33 +266,32 @@ describe('OHIF Study Viewer Page', function() {
|
||||
//cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||
});
|
||||
|
||||
it('performs right click to zoom', function() {
|
||||
// This is not used to activate the tool, it is used to ensure the
|
||||
// top left viewport info shows the zoom values (it only shows up
|
||||
// when the zoom tool is active)
|
||||
cy.get('@zoomBtn')
|
||||
.click()
|
||||
.then($zoomBtn => {
|
||||
cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
});
|
||||
// it('performs right click to zoom', function() {
|
||||
// // This is not used to activate the tool, it is used to ensure the
|
||||
// // top left viewport info shows the zoom values (it only shows up
|
||||
// // when the zoom tool is active)
|
||||
// cy.get('@zoomBtn')
|
||||
// .click()
|
||||
// .then($zoomBtn => {
|
||||
// cy.wrap($zoomBtn).should('have.class', 'active');
|
||||
// });
|
||||
|
||||
//Right click on viewport
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 3 })
|
||||
.trigger('mousemove', 'center', { which: 3 })
|
||||
.trigger('mouseup');
|
||||
// //Right click on viewport
|
||||
// cy.get('@viewport')
|
||||
// .trigger('mousedown', 'top', { buttons: 2 })
|
||||
// .trigger('mousemove', 'center', { buttons: 2 })
|
||||
// .trigger('mouseup');
|
||||
|
||||
// const expectedText = 'Zoom:6.65x';
|
||||
// cy.get('@viewportInfoTopLeft').should('contains.text', expectedText);
|
||||
// });
|
||||
|
||||
const expectedText = 'Zoom:3.98x';
|
||||
cy.get('@viewportInfoTopLeft').should('contains.text', expectedText);
|
||||
});
|
||||
|
||||
it('performs middle click to pan', function() {
|
||||
/*it('performs middle click to pan', function() {
|
||||
//Get image position from cornerstone and check if y axis was modified
|
||||
let cornerstone;
|
||||
let currentPan;
|
||||
|
||||
// TO DO: Replace the cornerstone pan check by Percy snapshop comparison
|
||||
// TO DO: Replace the cornerstone pan check by Percy snapshot comparison
|
||||
cy.window()
|
||||
.its('cornerstone')
|
||||
.then(c => {
|
||||
@@ -304,13 +302,13 @@ describe('OHIF Study Viewer Page', function() {
|
||||
|
||||
//pan image with middle click
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 2 })
|
||||
.trigger('mousemove', 'bottom', { which: 2 })
|
||||
.trigger('mousedown', 'center', { buttons: 3 })
|
||||
.trigger('mousemove', 'bottom', { buttons: 3 })
|
||||
.trigger('mouseup', 'bottom')
|
||||
.then(() => {
|
||||
expect(currentPan().y > 0).to.eq(true);
|
||||
});
|
||||
});
|
||||
});*/
|
||||
|
||||
/*it('opens About modal and verify the displayed information', function() {
|
||||
cy.get('[data-cy="options-dropdown"]')
|
||||
|
||||
@@ -2,22 +2,28 @@
|
||||
export function initCornerstoneToolsAliases() {
|
||||
cy.get('[data-cy="StackScroll"]').as('stackScrollBtn');
|
||||
cy.get('[data-cy="Zoom"]').as('zoomBtn');
|
||||
cy.get('[data-cy="Wwwc-split-button-primary"]').as('wwwcBtnPrimary');
|
||||
cy.get('[data-cy="Wwwc-split-button-secondary"]').as('wwwcBtnSecondary');
|
||||
cy.get('[data-cy="WindowLevel-split-button-primary"]').as('wwwcBtnPrimary');
|
||||
cy.get('[data-cy="WindowLevel-split-button-secondary"]').as(
|
||||
'wwwcBtnSecondary'
|
||||
);
|
||||
cy.get('[data-cy="Pan"]').as('panBtn');
|
||||
cy.get('[data-cy="MeasurementTools-split-button-primary"]').as('measurementToolsBtnPrimary');
|
||||
cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as('measurementToolsBtnSecondary');
|
||||
cy.get('[data-cy="Angle"]').as('angleBtn');
|
||||
cy.get('[data-cy="MeasurementTools-split-button-primary"]').as(
|
||||
'measurementToolsBtnPrimary'
|
||||
);
|
||||
cy.get('[data-cy="MeasurementTools-split-button-secondary"]').as(
|
||||
'measurementToolsBtnSecondary'
|
||||
);
|
||||
// cy.get('[data-cy="Angle"]').as('angleBtn');
|
||||
cy.get('[data-cy="MoreTools-split-button-primary"]').as('moreBtnPrimary');
|
||||
cy.get('[data-cy="MoreTools-split-button-secondary"]').as('moreBtnSecondary');
|
||||
cy.get('[data-cy="Layout"]').as('layoutBtn');
|
||||
cy.get('.viewport-element').as('viewport');
|
||||
cy.get('.cornerstone-viewport-element').as('viewport');
|
||||
}
|
||||
|
||||
//Creating aliases for Common page elements
|
||||
export function initCommonElementsAliases() {
|
||||
cy.get('[data-cy="trackedMeasurements-btn"]').as('measurementsBtn');
|
||||
cy.get('.viewport-element').as('viewport');
|
||||
cy.get('.cornerstone-viewport-element').as('viewport');
|
||||
cy.get('[data-cy="seriesList-btn"]').as('seriesBtn');
|
||||
|
||||
// TODO: Panels are not in DOM when closed, move this somewhere else
|
||||
@@ -25,8 +31,12 @@ export function initCommonElementsAliases() {
|
||||
cy.get('[data-cy="studyBrowser-panel"]').as('seriesPanel');
|
||||
cy.get('[data-cy="viewport-overlay-top-right"]').as('viewportInfoTopRight');
|
||||
cy.get('[data-cy="viewport-overlay-top-left"]').as('viewportInfoTopLeft');
|
||||
cy.get('[data-cy="viewport-overlay-bottom-right"]').as('viewportInfoBottomRight');
|
||||
cy.get('[data-cy="viewport-overlay-bottom-left"]').as('viewportInfoBottomLeft');
|
||||
cy.get('[data-cy="viewport-overlay-bottom-right"]').as(
|
||||
'viewportInfoBottomRight'
|
||||
);
|
||||
cy.get('[data-cy="viewport-overlay-bottom-left"]').as(
|
||||
'viewportInfoBottomLeft'
|
||||
);
|
||||
|
||||
cy.get('.left-mid.orientation-marker').as('viewportInfoMidLeft');
|
||||
cy.get('.top-mid.orientation-marker').as('viewportInfoMidTop');
|
||||
|
||||
@@ -89,7 +89,7 @@ Cypress.Commands.add('openStudyModality', Modality => {
|
||||
/**
|
||||
* Command to wait and check if a new page was loaded
|
||||
*
|
||||
* @param {string} url - part of the expected url. Default value is /viewer/
|
||||
* @param {string} url - part of the expected url. Default value is /viewer
|
||||
*/
|
||||
Cypress.Commands.add('isPageLoaded', (url = '/viewer') => {
|
||||
return cy.location('pathname', { timeout: 60000 }).should('include', url);
|
||||
@@ -139,9 +139,11 @@ Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
||||
|
||||
// TODO: Added a wait which appears necessary in Cornerstone Tools >4?
|
||||
cy.wrap($viewport)
|
||||
.click(x1, y1).wait(100)
|
||||
.click(x1, y1)
|
||||
.wait(100)
|
||||
.trigger('mousemove', { clientX: x2, clientY: y2 })
|
||||
.click(x2, y2).wait(100);
|
||||
.click(x2, y2)
|
||||
.wait(100);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -173,9 +175,11 @@ Cypress.Commands.add(
|
||||
);
|
||||
|
||||
Cypress.Commands.add('expectMinimumThumbnails', (seriesToWait = 1) => {
|
||||
cy.get('[data-cy="study-browser-thumbnail"]', { timeout: 50000 }).should($itemList => {
|
||||
expect($itemList.length >= seriesToWait).to.be.true;
|
||||
});
|
||||
cy.get('[data-cy="study-browser-thumbnail"]', { timeout: 50000 }).should(
|
||||
$itemList => {
|
||||
expect($itemList.length >= seriesToWait).to.be.true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
//Command to wait DICOM image to load into the viewport
|
||||
@@ -190,9 +194,12 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => {
|
||||
const onEvent = renderedEvt => {
|
||||
const element = renderedEvt.detail.element;
|
||||
|
||||
element.removeEventListener('cornerstoneimagerendered', onEvent);
|
||||
$cornerstone.events.removeEventListener(
|
||||
'cornerstoneimagerendered',
|
||||
element.removeEventListener(
|
||||
$cornerstone.Enums.Events.IMAGE_RENDERED,
|
||||
onEvent
|
||||
);
|
||||
$cornerstone.eventTarget.removeEventListener(
|
||||
$cornerstone.Enums.Events.IMAGE_RENDERED,
|
||||
onEvent
|
||||
);
|
||||
resolve();
|
||||
@@ -200,16 +207,24 @@ Cypress.Commands.add('waitDicomImage', (timeout = 50000) => {
|
||||
const onEnabled = enabledEvt => {
|
||||
const element = enabledEvt.detail.element;
|
||||
|
||||
element.addEventListener('cornerstoneimagerendered', onEvent);
|
||||
element.addEventListener(
|
||||
$cornerstone.Enums.Events.IMAGE_RENDERED,
|
||||
onEvent
|
||||
);
|
||||
|
||||
$cornerstone.eventTarget.removeEventListener(
|
||||
$cornerstone.Enums.Events.ELEMENT_ENABLED,
|
||||
onEnabled
|
||||
);
|
||||
};
|
||||
const enabledElements = $cornerstone.getEnabledElements();
|
||||
if (enabledElements && enabledElements.length && !enabledElements[0].invalid) {
|
||||
if (enabledElements && enabledElements.length) {
|
||||
// Sometimes the page finishes rendering before this gets run,
|
||||
// if so, just resolve immediately.
|
||||
resolve();
|
||||
} else {
|
||||
$cornerstone.events.addEventListener(
|
||||
'cornerstoneelementenabled',
|
||||
$cornerstone.eventTarget.addEventListener(
|
||||
$cornerstone.Enums.Events.ELEMENT_ENABLED,
|
||||
onEnabled
|
||||
);
|
||||
}
|
||||
@@ -224,8 +239,7 @@ Cypress.Commands.add('resetViewport', () => {
|
||||
cy.get('[data-cy="MoreTools-split-button-primary"]')
|
||||
.should('have.attr', 'data-tool', 'Reset')
|
||||
.as('moreBtn')
|
||||
.click()
|
||||
;
|
||||
.click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add('imageZoomIn', () => {
|
||||
@@ -234,8 +248,8 @@ Cypress.Commands.add('imageZoomIn', () => {
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'top', { which: 1 })
|
||||
.trigger('mousemove', 'center', { which: 1 })
|
||||
.trigger('mousedown', 'top', { buttons: 1 })
|
||||
.trigger('mousemove', 'center', { buttons: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
|
||||
@@ -245,8 +259,8 @@ Cypress.Commands.add('imageContrast', () => {
|
||||
|
||||
//drags the mouse inside the viewport to be able to interact with series
|
||||
cy.get('@viewport')
|
||||
.trigger('mousedown', 'center', { which: 1 })
|
||||
.trigger('mousemove', 'top', { which: 1 })
|
||||
.trigger('mousedown', 'center', { buttons: 1 })
|
||||
.trigger('mousemove', 'top', { buttons: 1 })
|
||||
.trigger('mouseup');
|
||||
});
|
||||
|
||||
@@ -509,13 +523,12 @@ Cypress.Commands.add(
|
||||
(function_label, shortcut) => {
|
||||
// Within scopes all `.get` and `.contains` to within the matched elements
|
||||
// dom instead of checking from document
|
||||
cy.get('.HotkeysPreferences')
|
||||
.within(() => {
|
||||
cy.contains(function_label) // label we're looking for
|
||||
.parent()
|
||||
.find('input') // closest input to that label
|
||||
.type(shortcut, { force: true }); // Set new shortcut for that function
|
||||
});
|
||||
cy.get('.HotkeysPreferences').within(() => {
|
||||
cy.contains(function_label) // label we're looking for
|
||||
.parent()
|
||||
.find('input') // closest input to that label
|
||||
.type(shortcut, { force: true }); // Set new shortcut for that function
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
# If 'production', `yarn install` does not install devDependencies
|
||||
NODE_ENV = "development"
|
||||
NODE_VERSION = "14.19.1"
|
||||
YARN_VERSION = "1.22.0"
|
||||
YARN_VERSION = "1.22.5"
|
||||
RUBY_VERSION = "2.6.2"
|
||||
YARN_FLAGS = "--no-ignore-optional --pure-lockfile"
|
||||
NETLIFY_USE_YARN = "true"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "5.0.0",
|
||||
"productVersion": "3.0.8",
|
||||
"productVersion": "3.2.0",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -30,6 +30,7 @@
|
||||
"dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js",
|
||||
"dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js",
|
||||
"dev:viewer": "yarn run dev",
|
||||
"preinstall": "node preinstall.js",
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
|
||||
@@ -44,30 +45,30 @@
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"cornerstone-core": "^2.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.3",
|
||||
"@ohif/core": "^3.0.0",
|
||||
"@ohif/extension-cornerstone": "^3.0.0",
|
||||
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
|
||||
"@ohif/extension-default": "^3.0.0",
|
||||
"@ohif/extension-dicom-sr": "^3.0.0",
|
||||
"@ohif/extension-measurement-tracking": "^3.0.0",
|
||||
"@ohif/extension-dicom-pdf": "^3.0.1",
|
||||
"@ohif/extension-dicom-video": "^3.0.1",
|
||||
"@ohif/i18n": "^1.0.0",
|
||||
"@ohif/mode-basic-dev-mode": "^3.0.0",
|
||||
"@ohif/mode-longitudinal": "^3.0.0",
|
||||
"@ohif/ui": "^2.0.0",
|
||||
"@types/react": "^16.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"config-point": "^0.4.8",
|
||||
"core-js": "^3.16.1",
|
||||
"cornerstone-math": "^0.1.9",
|
||||
"cornerstone-tools": "6.0.2",
|
||||
"cornerstone-wado-image-loader": "4.0.4",
|
||||
"dcmjs": "0.16.1",
|
||||
"cornerstone-wado-image-loader": "^4.2.0",
|
||||
"dcmjs": "^0.24.5",
|
||||
"detect-gpu": "^4.0.16",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"dotenv-webpack": "^1.7.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"history": "^5.2.0",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"lodash.isequal": "4.5.0",
|
||||
@@ -79,13 +80,13 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dropzone": "^10.1.7",
|
||||
"react-i18next": "^10.11.0",
|
||||
"react-resize-detector": "^4.2.0",
|
||||
"react-router": "^6.2.1",
|
||||
"react-router-dom": "^6.2.1"
|
||||
"react-resize-detector": "^6.7.6",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@percy/cypress": "^2.3.0",
|
||||
"cypress": "^6.4.0",
|
||||
"@percy/cypress": "^3.1.1",
|
||||
"cypress": "^9.5.4",
|
||||
"cypress-file-upload": "^3.5.3",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
"lodash": "4.17.15",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"packageName": "@ohif/extension-dicom-sr",
|
||||
"packageName": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
@@ -23,6 +23,10 @@
|
||||
{
|
||||
"packageName": "@ohif/extension-dicom-video",
|
||||
"version": "3.0.1"
|
||||
},
|
||||
{
|
||||
"packageName": "@ohif/extension-tmtv",
|
||||
"version": "3.0.0"
|
||||
}
|
||||
],
|
||||
"modes": [
|
||||
@@ -30,5 +34,9 @@
|
||||
"packageName": "@ohif/mode-longitudinal",
|
||||
"version": "3.0.0"
|
||||
}
|
||||
],
|
||||
"modesFactory": [
|
||||
],
|
||||
"umd": [
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
console.log('preinstall.js');
|
||||
|
||||
const { exec } = require('child_process');
|
||||
const log = (err, stdout, stderr) => console.log(stdout);
|
||||
|
||||
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
||||
if (GITHUB_TOKEN) {
|
||||
const command = `git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/".insteadOf ssh://git@github.com/`;
|
||||
console.log(command);
|
||||
exec(command, log);
|
||||
} else {
|
||||
console.log('No GITHUB_TOKEN found, skipping private repo customization');
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
# Ensure the examples are cross-origin isolated so that
|
||||
# we can use SharedArrayBuffer
|
||||
# See https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
@@ -83,12 +83,12 @@ window.config = {
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
|
||||
@@ -4,6 +4,12 @@ window.config = {
|
||||
extensions: [],
|
||||
modes: [],
|
||||
showStudyList: true,
|
||||
maxNumberOfWebWorkers: 3,
|
||||
maxNumRequests: {
|
||||
interaction: 100,
|
||||
thumbnail: 75,
|
||||
prefetch: 10,
|
||||
},
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
{
|
||||
@@ -81,12 +87,12 @@ window.config = {
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
|
||||
@@ -31,12 +31,12 @@ window.config = {
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
// whiteLabelling: {},
|
||||
extensions: [],
|
||||
modes: [],
|
||||
showStudyList: true,
|
||||
maxNumberOfWebWorkers: 3,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
{
|
||||
friendlyName: 'Static WADO Local Data',
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||
sourceName: 'dicomweb',
|
||||
configuration: {
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: '/dicomweb',
|
||||
qidoRoot: '/dicomweb',
|
||||
wadoRoot: '/dicomweb',
|
||||
qidoSupportsIncludeField: false,
|
||||
supportsReject: false,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: 'bulkdata,video,pdf',
|
||||
},
|
||||
},
|
||||
{
|
||||
friendlyName: 'dicom json',
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomjson',
|
||||
sourceName: 'dicomjson',
|
||||
configuration: {
|
||||
name: 'json',
|
||||
},
|
||||
},
|
||||
{
|
||||
friendlyName: 'dicom local',
|
||||
namespace: '@ohif/extension-default.dataSourcesModule.dicomlocal',
|
||||
sourceName: 'dicomlocal',
|
||||
configuration: {},
|
||||
},
|
||||
],
|
||||
httpErrorHandler: error => {
|
||||
// This is 429 when rejected from the public idc sandbox too often.
|
||||
console.warn(error.status);
|
||||
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
},
|
||||
// whiteLabeling: {
|
||||
// /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */
|
||||
// createLogoComponentFn: function (React) {
|
||||
// return React.createElement(
|
||||
// 'a',
|
||||
// {
|
||||
// target: '_self',
|
||||
// rel: 'noopener noreferrer',
|
||||
// className: 'text-purple-600 line-through',
|
||||
// href: '/',
|
||||
// },
|
||||
// React.createElement('img',
|
||||
// {
|
||||
// src: './customLogo.svg',
|
||||
// className: 'w-8 h-8',
|
||||
// }
|
||||
// ))
|
||||
// },
|
||||
// },
|
||||
defaultDataSourceName: 'dicomweb',
|
||||
hotkeys: [
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -102,12 +102,12 @@ window.config = {
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ window.config = {
|
||||
extensions: [],
|
||||
modes: [],
|
||||
showStudyList: true,
|
||||
maxNumberOfWebWorkers: 3,
|
||||
// filterQueryParam: false,
|
||||
dataSources: [
|
||||
{
|
||||
@@ -12,9 +13,9 @@ window.config = {
|
||||
sourceName: 'dicomweb',
|
||||
configuration: {
|
||||
name: 'DCM4CHEE',
|
||||
wadoUriRoot: '/dicomweb',
|
||||
qidoRoot: '/dicomweb',
|
||||
wadoRoot: '/dicomweb',
|
||||
wadoUriRoot: 'http://localhost:5000/dicomweb',
|
||||
qidoRoot: 'http://localhost:5000/dicomweb',
|
||||
wadoRoot: 'http://localhost:5000/dicomweb',
|
||||
qidoSupportsIncludeField: false,
|
||||
supportsReject: false,
|
||||
imageRendering: 'wadors',
|
||||
@@ -83,12 +84,12 @@ window.config = {
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ window.config = {
|
||||
supportsReject: true,
|
||||
imageRendering: 'wadors',
|
||||
thumbnailRendering: 'wadors',
|
||||
enableStudyLazyLoad: true,
|
||||
enableStudyLazyLoad: false,
|
||||
supportsFuzzyMatching: true,
|
||||
supportsWildcard: true,
|
||||
},
|
||||
@@ -81,12 +81,12 @@ window.config = {
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
|
||||
@@ -35,12 +35,12 @@ window.config = {
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
// Currently empty because nothing is configurable yet with themes
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
// External
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import Compose from './routes/Mode/Compose.js';
|
||||
import Compose from './routes/Mode/Compose.tsx';
|
||||
|
||||
import {
|
||||
DialogProvider,
|
||||
@@ -22,12 +22,26 @@ import {
|
||||
import { AppConfigProvider } from '@state';
|
||||
import createRoutes from './routes';
|
||||
import appInit from './appInit.js';
|
||||
import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes.jsx';
|
||||
import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes.tsx';
|
||||
|
||||
let commandsManager, extensionManager, servicesManager, hotkeysManager;
|
||||
|
||||
function App({ config, defaultExtensions, defaultModes }) {
|
||||
const init = appInit(config, defaultExtensions, defaultModes);
|
||||
const [init, setInit] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const run = async () => {
|
||||
appInit(config, defaultExtensions, defaultModes)
|
||||
.then(setInit)
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
run();
|
||||
}, []);
|
||||
|
||||
if (!init) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set above for named export
|
||||
commandsManager = init.commandsManager;
|
||||
@@ -45,7 +59,9 @@ function App({ config, defaultExtensions, defaultModes }) {
|
||||
dataSources,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
hotkeysManager,
|
||||
commandsManager,
|
||||
routerBasename,
|
||||
});
|
||||
const {
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ToolBarService,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
SegmentationService,
|
||||
CineService,
|
||||
UserAuthenticationService,
|
||||
errorHandler,
|
||||
@@ -22,7 +23,7 @@ import {
|
||||
* @param {object|func} appConfigOrFunc - application configuration, or a function that returns application configuration
|
||||
* @param {object[]} defaultExtensions - array of extension objects
|
||||
*/
|
||||
function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
|
||||
async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
|
||||
const appConfig = {
|
||||
...(typeof appConfigOrFunc === 'function'
|
||||
? appConfigOrFunc({ servicesManager })
|
||||
@@ -31,12 +32,6 @@ function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
|
||||
|
||||
const commandsManagerConfig = {
|
||||
getAppState: () => {},
|
||||
/** Used by commands to determine active context */
|
||||
getActiveContexts: () => [
|
||||
'VIEWER',
|
||||
'DEFAULT',
|
||||
'ACTIVE_VIEWPORT::CORNERSTONE',
|
||||
],
|
||||
};
|
||||
|
||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||
@@ -59,6 +54,7 @@ function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
|
||||
ToolBarService,
|
||||
ViewportGridService,
|
||||
HangingProtocolService,
|
||||
SegmentationService,
|
||||
CineService,
|
||||
UserAuthenticationService,
|
||||
]);
|
||||
@@ -73,7 +69,7 @@ function appInit(appConfigOrFunc, defaultExtensions, defaultModes) {
|
||||
* Example: [ext1, ext2, ext3]
|
||||
* Example2: [[ext1, config], ext2, [ext3, config]]
|
||||
*/
|
||||
extensionManager.registerExtensions(
|
||||
await extensionManager.registerExtensions(
|
||||
[...defaultExtensions, ...appConfig.extensions],
|
||||
appConfig.dataSources
|
||||
);
|
||||
|
||||
File renamed without changes.
@@ -1,286 +0,0 @@
|
||||
/**
|
||||
* CSS Grid Reference: http://grid.malven.co/
|
||||
*/
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
||||
import EmptyViewport from './EmptyViewport';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function ViewerViewportGrid(props) {
|
||||
const { servicesManager, viewportComponents, dataSource } = props;
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
const {
|
||||
numCols,
|
||||
numRows,
|
||||
activeViewportIndex,
|
||||
viewports,
|
||||
cachedLayout,
|
||||
} = viewportGrid;
|
||||
|
||||
// TODO -> Need some way of selecting which displaySets hit the viewports.
|
||||
const {
|
||||
DisplaySetService,
|
||||
MeasurementService,
|
||||
HangingProtocolService,
|
||||
} = servicesManager.services;
|
||||
|
||||
|
||||
const updateDisplaysetForViewports = useCallback(
|
||||
(displaySets) => {
|
||||
const [
|
||||
matchDetails,
|
||||
hpAlreadyApplied,
|
||||
] = HangingProtocolService.getState();
|
||||
|
||||
if (!matchDetails.length) return;
|
||||
// Match each viewport individually
|
||||
|
||||
const numViewports = viewportGrid.numRows * viewportGrid.numCols;
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
if (hpAlreadyApplied[i] === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if current viewport doesn't have a match
|
||||
if (matchDetails[i] === undefined) return
|
||||
|
||||
const { SeriesInstanceUID } = matchDetails[i];
|
||||
const matchingDisplaySet = displaySets.find(ds => {
|
||||
return ds.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (!matchingDisplaySet) {
|
||||
continue;
|
||||
}
|
||||
|
||||
viewportGridService.setDisplaysetForViewport({
|
||||
viewportIndex: i,
|
||||
displaySetInstanceUID: matchingDisplaySet.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
HangingProtocolService.setHangingProtocolAppliedForViewport(i);
|
||||
}
|
||||
},
|
||||
[viewportGrid, numRows, numCols],
|
||||
)
|
||||
|
||||
// Using Hanging protocol engine to match the displaySets
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
eventData => {
|
||||
const { displaySetsAdded } = eventData;
|
||||
updateDisplaysetForViewports(displaySetsAdded)
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewportGrid]);
|
||||
|
||||
|
||||
|
||||
// Changing the Hanging protocol while viewing
|
||||
useEffect(() => {
|
||||
const displaySets = DisplaySetService.getActiveDisplaySets();
|
||||
updateDisplaysetForViewports(displaySets)
|
||||
}, [viewportGrid])
|
||||
|
||||
|
||||
|
||||
|
||||
// Layout change based on hanging protocols
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = HangingProtocolService.subscribe(
|
||||
HangingProtocolService.EVENTS.NEW_LAYOUT,
|
||||
({ numRows, numCols }) => {
|
||||
viewportGridService.setLayout({ numRows, numCols });
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewports]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = MeasurementService.subscribe(
|
||||
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT,
|
||||
({ viewportIndex, measurement }) => {
|
||||
const referencedDisplaySetInstanceUID =
|
||||
measurement.displaySetInstanceUID;
|
||||
|
||||
const viewportsDisplaySetInstanceUIDs = viewports.map(
|
||||
vp => vp.displaySetInstanceUID
|
||||
);
|
||||
|
||||
// if we already have the displayset in one of the viewports
|
||||
if (
|
||||
viewportsDisplaySetInstanceUIDs.indexOf(
|
||||
referencedDisplaySetInstanceUID
|
||||
) > -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If not in any of the viewports, hang it inside the active viewport
|
||||
viewportGridService.setDisplaysetForViewport({
|
||||
viewportIndex,
|
||||
displaySetInstanceUID: referencedDisplaySetInstanceUID,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewports]);
|
||||
|
||||
const onDoubleClick = viewportIndex => {
|
||||
// TODO -> Disabled for now.
|
||||
// onNewImage on a cornerstone viewport is firing setDisplaySetForViewport.
|
||||
// Which it really really shouldn't. We need a larger fix for jump to
|
||||
// measurements and all cornerstone "imageIndex" state to fix this.
|
||||
if (cachedLayout) {
|
||||
viewportGridService.set({
|
||||
numCols: cachedLayout.numCols,
|
||||
numRows: cachedLayout.numRows,
|
||||
activeViewportIndex: cachedLayout.activeViewportIndex,
|
||||
viewports: cachedLayout.viewports,
|
||||
cachedLayout: null,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const cachedViewports = viewports.map(viewport => {
|
||||
return {
|
||||
displaySetInstanceUID: viewport.displaySetInstanceUID,
|
||||
};
|
||||
});
|
||||
|
||||
viewportGridService.set({
|
||||
numCols: 1,
|
||||
numRows: 1,
|
||||
activeViewportIndex: 0,
|
||||
viewports: [
|
||||
{
|
||||
displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID,
|
||||
imageIndex: undefined,
|
||||
},
|
||||
],
|
||||
cachedLayout: {
|
||||
numCols,
|
||||
numRows,
|
||||
viewports: cachedViewports,
|
||||
activeViewportIndex: viewportIndex,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => {
|
||||
viewportGridService.setDisplaysetForViewport({
|
||||
viewportIndex,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
};
|
||||
|
||||
const getViewportPanes = () => {
|
||||
const viewportPanes = [];
|
||||
const numViewportPanes = numCols * numRows;
|
||||
|
||||
for (let i = 0; i < numViewportPanes; i++) {
|
||||
const viewportIndex = i;
|
||||
const isActive = activeViewportIndex === viewportIndex;
|
||||
const paneMetadata = viewports[i] || {};
|
||||
const { displaySetInstanceUID } = paneMetadata;
|
||||
|
||||
const displaySet =
|
||||
DisplaySetService.getDisplaySetByUID(displaySetInstanceUID) || {};
|
||||
|
||||
const ViewportComponent = _getViewportComponent(
|
||||
displaySet.SOPClassHandlerId,
|
||||
viewportComponents
|
||||
);
|
||||
|
||||
const onInterationHandler = event => {
|
||||
if (isActive) return;
|
||||
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
viewportGridService.setActiveViewportIndex(viewportIndex);
|
||||
};
|
||||
|
||||
// TEMP -> Double click disabled for now
|
||||
// onDoubleClick={() => onDoubleClick(viewportIndex)}
|
||||
|
||||
viewportPanes[i] = (
|
||||
<ViewportPane
|
||||
key={viewportIndex}
|
||||
className="m-1"
|
||||
acceptDropsFor="displayset"
|
||||
onDrop={onDropHandler.bind(null, viewportIndex)}
|
||||
onInteraction={onInterationHandler}
|
||||
isActive={isActive}
|
||||
>
|
||||
<div
|
||||
className={classNames('h-full w-full flex flex-col', {
|
||||
'pointer-events-none': !isActive,
|
||||
})}
|
||||
>
|
||||
<ViewportComponent
|
||||
displaySet={displaySet}
|
||||
viewportIndex={viewportIndex}
|
||||
dataSource={dataSource}
|
||||
/>
|
||||
</div>
|
||||
</ViewportPane>
|
||||
);
|
||||
}
|
||||
|
||||
return viewportPanes;
|
||||
};
|
||||
|
||||
if (!numCols || !numCols) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewportGrid numRows={numRows} numCols={numCols}>
|
||||
{/* {ViewportPanes} */}
|
||||
{getViewportPanes()}
|
||||
</ViewportGrid>
|
||||
);
|
||||
}
|
||||
|
||||
ViewerViewportGrid.propTypes = {
|
||||
viewportComponents: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
ViewerViewportGrid.defaultProps = {
|
||||
viewportComponents: [],
|
||||
};
|
||||
|
||||
function _getViewportComponent(SOPClassHandlerId, viewportComponents) {
|
||||
if (!SOPClassHandlerId) {
|
||||
return EmptyViewport;
|
||||
}
|
||||
|
||||
for (let i = 0; i < viewportComponents.length; i++) {
|
||||
if (
|
||||
viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId)
|
||||
) {
|
||||
const { component } = viewportComponents[i];
|
||||
return component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ViewerViewportGrid;
|
||||
@@ -0,0 +1,381 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
||||
import EmptyViewport from './EmptyViewport';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function ViewerViewportGrid(props) {
|
||||
const { servicesManager, viewportComponents, dataSource } = props;
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
const { numCols, numRows, activeViewportIndex, viewports } = viewportGrid;
|
||||
|
||||
// TODO -> Need some way of selecting which displaySets hit the viewports.
|
||||
const {
|
||||
DisplaySetService,
|
||||
MeasurementService,
|
||||
HangingProtocolService,
|
||||
} = servicesManager.services;
|
||||
|
||||
/**
|
||||
* This callback runs only after displaySets have changed (created and added or modified)
|
||||
*/
|
||||
const updateDisplaySetsForViewports = useCallback(
|
||||
availableDisplaySets => {
|
||||
if (!availableDisplaySets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [
|
||||
matchDetails,
|
||||
hpAlreadyApplied,
|
||||
] = HangingProtocolService.getState();
|
||||
|
||||
if (!matchDetails.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Match each viewport individually
|
||||
const numViewports = viewportGrid.numRows * viewportGrid.numCols;
|
||||
for (let i = 0; i < numViewports; i++) {
|
||||
if (hpAlreadyApplied[i] === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if current viewport doesn't have a match
|
||||
if (matchDetails[i] === undefined) return;
|
||||
|
||||
const { displaySetsInfo, viewportOptions } = matchDetails[i];
|
||||
|
||||
const displaySetUIDsToHang = [];
|
||||
const displaySetUIDsToHangOptions = [];
|
||||
displaySetsInfo.forEach(({ SeriesInstanceUID, displaySetOptions }) => {
|
||||
const matchingDisplaySet = availableDisplaySets.find(ds => {
|
||||
return ds.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (!matchingDisplaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
displaySetUIDsToHang.push(matchingDisplaySet.displaySetInstanceUID);
|
||||
displaySetUIDsToHangOptions.push(displaySetOptions);
|
||||
});
|
||||
|
||||
if (!displaySetUIDsToHang.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportIndex: i,
|
||||
displaySetInstanceUIDs: displaySetUIDsToHang,
|
||||
viewportOptions,
|
||||
displaySetOptions: displaySetUIDsToHangOptions,
|
||||
});
|
||||
|
||||
// During setting displaySets for viewport, we need to update the hanging protocol
|
||||
// but some viewports contain more than one display set (fusion), and their displaySet
|
||||
// will not be available at the time of setting displaySets for viewport. So we need to
|
||||
// update the hanging protocol after making sure all the matched display sets are available
|
||||
// and set on the viewport
|
||||
if (displaySetUIDsToHang.length === displaySetsInfo.length) {
|
||||
// The following will set the hpAlreadyApplied state
|
||||
HangingProtocolService.setHangingProtocolAppliedForViewport(i);
|
||||
}
|
||||
}
|
||||
},
|
||||
[viewportGrid, numRows, numCols]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const displaySets = DisplaySetService.getActiveDisplaySets();
|
||||
updateDisplaySetsForViewports(displaySets);
|
||||
}, [numRows, numCols]);
|
||||
|
||||
// Layout change based on hanging protocols
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = HangingProtocolService.subscribe(
|
||||
HangingProtocolService.EVENTS.NEW_LAYOUT,
|
||||
({ layoutType, numRows, numCols, layoutOptions }) => {
|
||||
viewportGridService.setLayout({
|
||||
numRows,
|
||||
numCols,
|
||||
layoutType,
|
||||
layoutOptions,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewports]);
|
||||
|
||||
// Using Hanging protocol engine to match the displaySets
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED,
|
||||
activeDisplaySets => {
|
||||
updateDisplaySetsForViewports(activeDisplaySets);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewports]);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = MeasurementService.subscribe(
|
||||
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT,
|
||||
({ viewportIndex, measurement }) => {
|
||||
const referencedDisplaySetInstanceUID =
|
||||
measurement.displaySetInstanceUID;
|
||||
|
||||
// if we already have the displaySet in one of the viewports
|
||||
// Todo: handle fusion display sets?
|
||||
for (const viewport of viewports) {
|
||||
const isMatch = viewport.displaySetInstanceUIDs.includes(
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
if (isMatch) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const displaySet = DisplaySetService.getDisplaySetByUID(
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
let imageIndex;
|
||||
// jump straight to the initial image index if we can
|
||||
if (displaySet.images && measurement.SOPInstanceUID) {
|
||||
imageIndex = displaySet.images.findIndex(
|
||||
image => image.SOPInstanceUID === measurement.SOPInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
// If not in any of the viewports, hang it inside the active viewport
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportIndex,
|
||||
displaySetInstanceUIDs: [referencedDisplaySetInstanceUID],
|
||||
viewportOptions: {
|
||||
initialImageOptions: {
|
||||
index: imageIndex,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewports]);
|
||||
|
||||
/**
|
||||
//Changing the Hanging protocol while viewing
|
||||
useEffect(() => {
|
||||
const displaySets = DisplaySetService.getActiveDisplaySets();
|
||||
updateDisplaySetsForViewports(displaySets);
|
||||
}, [viewports]);
|
||||
|
||||
|
||||
// subscribe to displayset metadata changes
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_METADATA_UPDATED,
|
||||
displaySets => {
|
||||
// Todo: properly refresh the viewportGrid to use the new displaySet
|
||||
// with the new metadata.
|
||||
setState({});
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewports]);
|
||||
|
||||
const onDoubleClick = viewportIndex => {
|
||||
// TODO -> Disabled for now.
|
||||
// onNewImage on a cornerstone viewport is firing setDisplaySetsForViewport.
|
||||
// Which it really really shouldn't. We need a larger fix for jump to
|
||||
// measurements and all cornerstone "imageIndex" state to fix this.
|
||||
if (cachedLayout) {
|
||||
viewportGridService.set({
|
||||
numCols: cachedLayout.numCols,
|
||||
numRows: cachedLayout.numRows,
|
||||
activeViewportIndex: cachedLayout.activeViewportIndex,
|
||||
viewports: cachedLayout.viewports,
|
||||
cachedLayout: null,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const cachedViewports = viewports.map(viewport => {
|
||||
return {
|
||||
displaySetInstanceUID: viewport.displaySetInstanceUID,
|
||||
};
|
||||
});
|
||||
|
||||
viewportGridService.set({
|
||||
numCols: 1,
|
||||
numRows: 1,
|
||||
activeViewportIndex: 0,
|
||||
viewports: [
|
||||
{
|
||||
displaySetInstanceUID: viewports[viewportIndex].displaySetInstanceUID,
|
||||
imageIndex: undefined,
|
||||
},
|
||||
],
|
||||
cachedLayout: {
|
||||
numCols,
|
||||
numRows,
|
||||
viewports: cachedViewports,
|
||||
activeViewportIndex: viewportIndex,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
const onDropHandler = (viewportIndex, { displaySetInstanceUID }) => {
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportIndex,
|
||||
displaySetInstanceUIDs: [displaySetInstanceUID],
|
||||
});
|
||||
};
|
||||
|
||||
const getViewportPanes = useCallback(() => {
|
||||
const viewportPanes = [];
|
||||
|
||||
for (let i = 0; i < viewports.length; i++) {
|
||||
const viewportIndex = i;
|
||||
const isActive = activeViewportIndex === viewportIndex;
|
||||
const paneMetadata = viewports[i] || {};
|
||||
const {
|
||||
displaySetInstanceUIDs,
|
||||
viewportOptions,
|
||||
displaySetOptions, // array of options for each display set in the viewport
|
||||
x: viewportX,
|
||||
y: viewportY,
|
||||
width: viewportWidth,
|
||||
height: viewportHeight,
|
||||
viewportLabel,
|
||||
} = paneMetadata;
|
||||
|
||||
const displaySetInstanceUIDsToUse = displaySetInstanceUIDs || [];
|
||||
|
||||
// This is causing the viewport components re-render when the activeViewportIndex changes
|
||||
const displaySets = displaySetInstanceUIDsToUse.map(
|
||||
displaySetInstanceUID => {
|
||||
return (
|
||||
DisplaySetService.getDisplaySetByUID(displaySetInstanceUID) || {}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const ViewportComponent = _getViewportComponent(
|
||||
displaySets,
|
||||
viewportComponents
|
||||
);
|
||||
|
||||
// look inside displaySets to see if they need reRendering
|
||||
const displaySetsNeedsRerendering = displaySets.some(displaySet => {
|
||||
return displaySet.needsRerendering;
|
||||
});
|
||||
|
||||
const onInteractionHandler = event => {
|
||||
if (isActive) return;
|
||||
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
viewportGridService.setActiveViewportIndex(viewportIndex);
|
||||
};
|
||||
|
||||
// TEMP -> Double click disabled for now
|
||||
// onDoubleClick={() => onDoubleClick(viewportIndex)}
|
||||
|
||||
viewportPanes[i] = (
|
||||
<ViewportPane
|
||||
key={viewportIndex}
|
||||
acceptDropsFor="displayset"
|
||||
onDrop={onDropHandler.bind(null, viewportIndex)}
|
||||
onInteraction={onInteractionHandler}
|
||||
customStyle={{
|
||||
position: 'absolute',
|
||||
top: viewportY * 100 + 0.2 + '%',
|
||||
left: viewportX * 100 + 0.2 + '%',
|
||||
width: viewportWidth * 100 - 0.3 + '%',
|
||||
height: viewportHeight * 100 - 0.3 + '%',
|
||||
}}
|
||||
isActive={isActive}
|
||||
>
|
||||
<div
|
||||
className={classNames('h-full w-full flex flex-col', {
|
||||
'pointer-events-none': !isActive,
|
||||
})}
|
||||
>
|
||||
<ViewportComponent
|
||||
displaySets={displaySets}
|
||||
viewportIndex={viewportIndex}
|
||||
viewportLabel={viewports.length > 1 ? viewportLabel : ''}
|
||||
dataSource={dataSource}
|
||||
viewportOptions={viewportOptions}
|
||||
displaySetOptions={displaySetOptions}
|
||||
needsRerendering={displaySetsNeedsRerendering}
|
||||
/>
|
||||
</div>
|
||||
</ViewportPane>
|
||||
);
|
||||
}
|
||||
|
||||
return viewportPanes;
|
||||
}, [viewports, activeViewportIndex, viewportComponents, dataSource]);
|
||||
|
||||
/**
|
||||
* Loading indicator until numCols and numRows are gotten from the HangingProtocolService
|
||||
*/
|
||||
if (!numRows || !numCols) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewportGrid numRows={numRows} numCols={numCols}>
|
||||
{/* {ViewportPanes} */}
|
||||
{getViewportPanes()}
|
||||
</ViewportGrid>
|
||||
);
|
||||
}
|
||||
|
||||
ViewerViewportGrid.propTypes = {
|
||||
viewportComponents: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
ViewerViewportGrid.defaultProps = {
|
||||
viewportComponents: [],
|
||||
};
|
||||
|
||||
function _getViewportComponent(displaySets, viewportComponents) {
|
||||
if (!displaySets || !displaySets.length) {
|
||||
return EmptyViewport;
|
||||
}
|
||||
|
||||
// Todo: Do we have a viewport that has two different SOPClassHandlerIds?
|
||||
const SOPClassHandlerId = displaySets[0].SOPClassHandlerId;
|
||||
|
||||
for (let i = 0; i < viewportComponents.length; i++) {
|
||||
if (
|
||||
viewportComponents[i].displaySetsToDisplay.includes(SOPClassHandlerId)
|
||||
) {
|
||||
const { component } = viewportComponents[i];
|
||||
return component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ViewerViewportGrid;
|
||||
@@ -2,7 +2,7 @@
|
||||
* Entry point for development and production PWA builds.
|
||||
*/
|
||||
import 'regenerator-runtime/runtime';
|
||||
import App from './App.jsx';
|
||||
import App from './App.tsx';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
@@ -15,19 +15,21 @@ import ReactDOM from 'react-dom';
|
||||
* pluginImports.js imports all of the modes and extensions and adds them
|
||||
* to the window for processing.
|
||||
*/
|
||||
import './pluginImports.js';
|
||||
import loadDynamicImports from './pluginImports.js';
|
||||
|
||||
/**
|
||||
* Combine our appConfiguration with installed extensions and modes.
|
||||
* In the future appConfiguration may contain modes added at runtime.
|
||||
* */
|
||||
const appProps = {
|
||||
config: window ? window.config : {},
|
||||
defaultExtensions: window.extensions,
|
||||
defaultModes: window.modes,
|
||||
};
|
||||
loadDynamicImports().then(() => {
|
||||
/**
|
||||
* Combine our appConfiguration with installed extensions and modes.
|
||||
* In the future appConfiguration may contain modes added at runtime.
|
||||
* */
|
||||
const appProps = {
|
||||
config: window ? window.config : {},
|
||||
defaultExtensions: window.extensions,
|
||||
defaultModes: window.modes,
|
||||
};
|
||||
|
||||
/** Create App */
|
||||
const app = React.createElement(App, appProps, null);
|
||||
/** Render */
|
||||
ReactDOM.render(app, document.getElementById('root'));
|
||||
/** Create App */
|
||||
const app = React.createElement(App, appProps, null);
|
||||
/** Render */
|
||||
ReactDOM.render(app, document.getElementById('root'));
|
||||
});
|
||||
+1
-2
@@ -11,8 +11,7 @@ function CallbackPage({ userManager, onRedirectSuccess }) {
|
||||
.then(user => onRedirectSuccess(user))
|
||||
.catch(error => onRedirectError(error));
|
||||
|
||||
// todo: add i18n (or return null?)
|
||||
return <div>Redirecting...</div>;
|
||||
return null;
|
||||
}
|
||||
|
||||
CallbackPage.propTypes = {
|
||||
+6
-5
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { MODULE_TYPES } from '@ohif/core';
|
||||
//
|
||||
import { extensionManager } from '../App.jsx';
|
||||
import { extensionManager } from '../App.tsx';
|
||||
import { useParams, useLocation } from 'react-router';
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,7 @@ function DataSourceWrapper(props) {
|
||||
// Grabbing first for now - should get active?
|
||||
const name = webApiDataSources[0].name;
|
||||
// TODO: Why does this return an array?
|
||||
const dataSource = extensionManager.getDataSources(name)[0]
|
||||
const dataSource = extensionManager.getDataSources(name)[0];
|
||||
|
||||
// Route props --> studies.mapParams
|
||||
// mapParams --> studies.search
|
||||
@@ -86,12 +86,13 @@ function DataSourceWrapper(props) {
|
||||
const newOffset =
|
||||
Math.floor(
|
||||
(queryFilterValues.pageNumber * queryFilterValues.resultsPerPage) /
|
||||
STUDIES_LIMIT
|
||||
STUDIES_LIMIT
|
||||
) *
|
||||
(STUDIES_LIMIT - 1);
|
||||
const isLocationUpdated = data.location !== location;
|
||||
const isDataInvalid = !isSamePage || !isLoading &&
|
||||
(newOffset !== previousOffset || isLocationUpdated);
|
||||
const isDataInvalid =
|
||||
!isSamePage ||
|
||||
(!isLoading && (newOffset !== previousOffset || isLocationUpdated));
|
||||
|
||||
if (isDataInvalid) {
|
||||
getData();
|
||||
+31
-30
@@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { MODULE_TYPES } from '@ohif/core'
|
||||
import { MODULE_TYPES } from '@ohif/core';
|
||||
|
||||
import Dropzone from 'react-dropzone'
|
||||
import filesToStudies from './filesToStudies'
|
||||
import Dropzone from 'react-dropzone';
|
||||
import filesToStudies from './filesToStudies';
|
||||
|
||||
import { extensionManager } from '../../App.jsx'
|
||||
import { extensionManager } from '../../App.tsx';
|
||||
|
||||
import { Icon, Button } from '@ohif/ui'
|
||||
import { Icon, Button } from '@ohif/ui';
|
||||
|
||||
const getLoadButton = (onDrop, text, isDir) => {
|
||||
return (
|
||||
@@ -21,7 +21,7 @@ const getLoadButton = (onDrop, text, isDir) => {
|
||||
disabled={false}
|
||||
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
|
||||
className={classnames('font-bold', 'ml-2')}
|
||||
onClick={() => { }}
|
||||
onClick={() => {}}
|
||||
>
|
||||
{text}
|
||||
{isDir ? (
|
||||
@@ -36,41 +36,42 @@ const getLoadButton = (onDrop, text, isDir) => {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>)
|
||||
}
|
||||
</Dropzone>
|
||||
);
|
||||
};
|
||||
|
||||
function Local() {
|
||||
const navigate = useNavigate();
|
||||
const dropzoneRef = useRef()
|
||||
const dropzoneRef = useRef();
|
||||
|
||||
// Initializing the dicom local dataSource
|
||||
const dataSourceModules = extensionManager.modules[MODULE_TYPES.DATA_SOURCE]
|
||||
const dataSourceModules = extensionManager.modules[MODULE_TYPES.DATA_SOURCE];
|
||||
const localDataSources = dataSourceModules.reduce((acc, curr) => {
|
||||
const mods = []
|
||||
curr.module.forEach((mod) => {
|
||||
const mods = [];
|
||||
curr.module.forEach(mod => {
|
||||
if (mod.type === 'localApi') {
|
||||
mods.push(mod)
|
||||
mods.push(mod);
|
||||
}
|
||||
})
|
||||
return acc.concat(mods)
|
||||
}, [])
|
||||
});
|
||||
return acc.concat(mods);
|
||||
}, []);
|
||||
|
||||
const firstLocalDataSource = localDataSources[0]
|
||||
const dataSource = firstLocalDataSource.createDataSource({})
|
||||
const firstLocalDataSource = localDataSources[0];
|
||||
const dataSource = firstLocalDataSource.createDataSource({});
|
||||
|
||||
const onDrop = async (acceptedFiles) => {
|
||||
const studies = await filesToStudies(acceptedFiles, dataSource)
|
||||
const onDrop = async acceptedFiles => {
|
||||
const studies = await filesToStudies(acceptedFiles, dataSource);
|
||||
// Todo: navigate to work list and let user select a mode
|
||||
navigate(`/viewer/dicomlocal?StudyInstanceUIDs=${studies[0]}`)
|
||||
}
|
||||
navigate(`/viewer/dicomlocal?StudyInstanceUIDs=${studies[0]}`);
|
||||
};
|
||||
|
||||
// Set body style
|
||||
useEffect(() => {
|
||||
document.body.classList.add('bg-black')
|
||||
document.body.classList.add('bg-black');
|
||||
return () => {
|
||||
document.body.classList.remove('bg-black')
|
||||
}
|
||||
}, [])
|
||||
document.body.classList.remove('bg-black');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dropzone ref={dropzoneRef} onDrop={onDrop} noClick>
|
||||
@@ -104,7 +105,7 @@ function Local() {
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Local
|
||||
export default Local;
|
||||
File renamed without changes.
+10
-6
@@ -61,6 +61,7 @@ export default function ModeRoute({
|
||||
dataSourceName,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
hotkeysManager,
|
||||
}) {
|
||||
// Parse route params/querystring
|
||||
@@ -197,16 +198,20 @@ export default function ModeRoute({
|
||||
return;
|
||||
}
|
||||
// TODO: For some reason this is running before the Providers
|
||||
// are calling setServiceImplementationf
|
||||
// TOOD -> iterate through services.
|
||||
// are calling setServiceImplementation
|
||||
// TODO -> iterate through services.
|
||||
|
||||
// Extension
|
||||
|
||||
// Add SOPClassHandlers to a new SOPClassManager.
|
||||
DisplaySetService.init(extensionManager, sopClassHandlers);
|
||||
|
||||
extensionManager.onModeEnter();
|
||||
mode?.onModeEnter({ servicesManager, extensionManager });
|
||||
extensionManager.onModeEnter({
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
});
|
||||
mode?.onModeEnter({ servicesManager, extensionManager, commandsManager });
|
||||
|
||||
// Adding hanging protocols of extensions after onModeEnter since
|
||||
// it will reset the protocols
|
||||
@@ -214,6 +219,7 @@ export default function ModeRoute({
|
||||
const hangingProtocolModule = extensionManager.getModuleEntry(
|
||||
extensionProtocols
|
||||
);
|
||||
|
||||
if (hangingProtocolModule?.protocols) {
|
||||
HangingProtocolService.addProtocols(hangingProtocolModule.protocols);
|
||||
}
|
||||
@@ -232,8 +238,6 @@ export default function ModeRoute({
|
||||
|
||||
return await defaultRouteInit({
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
hotkeysManager,
|
||||
studyInstanceUIDs,
|
||||
dataSource,
|
||||
});
|
||||
File renamed without changes.
File renamed without changes.
+1
-2
@@ -22,8 +22,7 @@ function SignoutCallbackComponent({ userManager }) {
|
||||
.then(user => onRedirectSuccess(user))
|
||||
.catch(error => onRedirectError(error));
|
||||
|
||||
// todo: add i18n
|
||||
return <div>Redirecting...</div>;
|
||||
return null;
|
||||
}
|
||||
|
||||
SignoutCallbackComponent.propTypes = {
|
||||
+4
-3
@@ -12,8 +12,6 @@ import { useAppConfig } from '@state';
|
||||
import { useDebounce, useQuery } from '@hooks';
|
||||
import { utils, hotkeys } from '@ohif/core';
|
||||
|
||||
const { sortBySeriesDate } = utils;
|
||||
|
||||
import {
|
||||
Icon,
|
||||
StudyListExpandedRow,
|
||||
@@ -31,6 +29,8 @@ import {
|
||||
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
const { sortBySeriesDate } = utils;
|
||||
|
||||
const { availableLanguages, defaultLanguage, currentLanguage } = i18n;
|
||||
|
||||
const seriesInStudiesMap = new Map();
|
||||
@@ -309,6 +309,8 @@ function WorkList({
|
||||
gridCol: 4,
|
||||
},
|
||||
],
|
||||
// Todo: This is actually running for all rows, even if they are
|
||||
// not clicked on.
|
||||
expandedContent: (
|
||||
<StudyListExpandedRow
|
||||
seriesTableColumns={{
|
||||
@@ -334,7 +336,6 @@ function WorkList({
|
||||
const isFirst = i === 0;
|
||||
|
||||
const isValidMode = mode.isValidMode({ modalities });
|
||||
|
||||
// TODO: Modes need a default/target route? We mostly support a single one for now.
|
||||
// We should also be using the route path, but currently are not
|
||||
// mode.routeName
|
||||
+5
@@ -27,6 +27,7 @@ export default function buildModeRoutes({
|
||||
dataSources,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
hotkeysManager,
|
||||
}) {
|
||||
const routes = [];
|
||||
@@ -58,7 +59,9 @@ export default function buildModeRoutes({
|
||||
dataSourceName={dataSourceName}
|
||||
extensionManager={extensionManager}
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
hotkeysManager={hotkeysManager}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -80,7 +83,9 @@ export default function buildModeRoutes({
|
||||
dataSourceName={defaultDataSourceName}
|
||||
extensionManager={extensionManager}
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
hotkeysManager={hotkeysManager}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -33,6 +33,7 @@ const createRoutes = ({
|
||||
dataSources,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
hotkeysManager,
|
||||
routerBasename,
|
||||
}) => {
|
||||
@@ -42,6 +43,7 @@ const createRoutes = ({
|
||||
dataSources,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
hotkeysManager,
|
||||
}) || [];
|
||||
|
||||
@@ -67,7 +69,7 @@ const createRoutes = ({
|
||||
// Note: PrivateRoutes in react-router-dom 6.x should be defined within
|
||||
// a Route element
|
||||
return (
|
||||
<Routes basename={routerBasename}>
|
||||
<Routes>
|
||||
{allRoutes.map((route, i) => {
|
||||
return route.private === true ? (
|
||||
<Route
|
||||
@@ -10,7 +10,7 @@ workbox.core.clientsClaim();
|
||||
|
||||
// Cache static assets that aren't precached
|
||||
workbox.routing.registerRoute(
|
||||
/\.(?:js|css)$/,
|
||||
/\.(?:js|css|json5)$/,
|
||||
new workbox.strategies.StaleWhileRevalidate({
|
||||
cacheName: 'static-resources',
|
||||
})
|
||||
|
||||
File renamed without changes.
@@ -1,3 +1,3 @@
|
||||
import { AppConfigProvider, useAppConfig } from './appConfig.js';
|
||||
import { AppConfigProvider, useAppConfig } from './appConfig.tsx';
|
||||
|
||||
export { AppConfigProvider, useAppConfig };
|
||||
File renamed without changes.
@@ -6,11 +6,11 @@ module.exports = {
|
||||
// Note: if you are writing an extension that uses Tailwind classes,
|
||||
// you need to put it here so they are not purged in the production build
|
||||
content: [
|
||||
'./src/**/*.{jsx,js,css}',
|
||||
'./node_modules/@ohif/ui/src/**/*.{js,jsx,css}',
|
||||
'../../node_modules/@ohif/ui/src/**/*.{js,jsx,css}',
|
||||
'../../node_modules/@ohif/ui/src/**/*.{js,jsx,css}',
|
||||
'../../node_modules/@ohif/extension-*/src/**/*.{js,jsx,css}',
|
||||
'./src/**/*.{jsx,js,ts,tsx, css}',
|
||||
'./node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx, css}',
|
||||
'../../node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}',
|
||||
'../../node_modules/@ohif/ui/src/**/*.{js,jsx,ts,tsx,css}',
|
||||
'../../node_modules/@ohif/extension-*/src/**/*.{js,jsx,css, ts,tsx}',
|
||||
],
|
||||
},
|
||||
theme: {
|
||||
|
||||
Reference in new issue
Block a user