fix(cli): better support for external modules and toolGroups in templates (#3364)

* choe(modes, components): improved readability

This commit includes changes to improve performance in the basic-dev-mode and Thumbnail components.
Additionally, a change in the webpack configuration file was made to ignore ".min.js.map" files.
The defaultDataSourceName was also added to several configuration files, and whiteLabeling code was removed
from the local_orthanc configuration file.

* fix(cli): better path management and add dependencies

* better docs

* add default tool group to the templates

* add review comments

* apply review comments

* try to fix test

* fix thumbnail in tests
This commit is contained in:
Alireza 2023-05-08 15:49:28 -04:00 committed by GitHub
parent 187e6b5f30
commit 2f355fa158
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 193 additions and 142 deletions

View File

@ -51,6 +51,10 @@ export default class ToolGroupService {
Object.assign(this, pubSubServiceInterface);
}
onModeExit() {
this.destroy();
}
/**
* Retrieves a tool group from the ToolGroupManager by tool group ID.
* If no tool group ID is provided, it retrieves the tool group of the active viewport.

View File

@ -1,5 +1,5 @@
import toolbarButtons from './toolbarButtons.js';
import { hotkeys, ServicesManager } from '@ohif/core';
import { hotkeys } from '@ohif/core';
import { id } from './id';
const configs = {
@ -134,12 +134,6 @@ function modeFactory({ modeConfiguration }) {
'MoreTools',
]);
},
onModeExit: ({ servicesManager }) => {
const { toolGroupService, measurementService, toolbarService } =
servicesManager.services;
toolGroupService.destroy();
},
validationTags: {
study: [],
series: [],

View File

@ -254,3 +254,4 @@ const mode = {
};
export default mode;
export { initToolGroups, toolbarButtons };

View File

@ -41,9 +41,37 @@ async function linkPackage(packageDir, options, addToConfig, keyword) {
results = await execa(`yarn`, ['link', packageName]);
console.log(results.stdout);
// Add the node_modules of the linked package so that webpack
// can find the linked package externals if there are
const webpackPwaPath = path.join(
viewerDirectory,
'.webpack',
'webpack.pwa.js'
);
async function updateWebpackConfig(webpackConfigPath, packageDir) {
const packageNodeModules = path.join(packageDir, 'node_modules');
const fileContent = await fs.promises.readFile(webpackConfigPath, 'utf8');
const newLine = `path.resolve(__dirname, '${packageNodeModules}'),`;
const modifiedFileContent = fileContent.replace(
/(modules:\s*\[)([\s\S]*?)(\])/,
`$1$2 ${newLine}$3`
);
await fs.promises.writeFile(webpackConfigPath, modifiedFileContent);
}
await updateWebpackConfig(webpackPwaPath, packageDir);
// change directory to viewer packages and add the config item
process.chdir(viewerDirectory);
addToConfig(packageName, { version });
addToConfig(packageName, {
version,
});
// run prettier on the webpack config
results = await execa(`yarn`, ['prettier', '--write', webpackPwaPath]);
}
function linkExtension(packageDir, options) {

View File

@ -1,4 +1,6 @@
import { execa } from 'execa';
import fs from 'fs';
import path from 'path';
import {
validateYarn,
removeExtensionFromConfig,
@ -17,10 +19,51 @@ const linkPackage = async (packageName, options, removeFromConfig) => {
const results = await execa(`yarn`, ['unlink', packageName]);
console.log(results.stdout);
const webpackPwaPath = path.join(
viewerDirectory,
'.webpack',
'webpack.pwa.js'
);
await removePathFromWebpackConfig(webpackPwaPath, packageName);
//update the plugin.json file
removeFromConfig(packageName);
// run prettier on the webpack config
await execa(`yarn`, ['prettier', '--write', webpackPwaPath]);
};
async function removePathFromWebpackConfig(webpackConfigPath, packageName) {
const fileContent = await fs.promises.readFile(webpackConfigPath, 'utf8');
const packageNameSubstring = `${packageName}/node_modules`;
const pathResolveStart = 'path.resolve(';
const closingParenthesis = ')';
let startIndex = fileContent.indexOf(packageNameSubstring);
if (startIndex === -1) {
return;
}
// Find the start of the "path.resolve" line.
startIndex = fileContent.lastIndexOf(pathResolveStart, startIndex);
// Find the end of the line with the closing parenthesis.
let endIndex = fileContent.indexOf(closingParenthesis, startIndex) + 1;
// Check if there's a comma after the closing parenthesis and remove it as well.
if (fileContent[endIndex] === ',') {
endIndex++;
}
const modifiedFileContent =
fileContent.slice(0, startIndex) + fileContent.slice(endIndex);
await fs.promises.writeFile(webpackConfigPath, modifiedFileContent);
}
function unlinkExtension(extensionName, options) {
linkPackage(extensionName, options, removeExtensionFromConfig);
}

View File

@ -6,9 +6,9 @@ export default function writePluginConfigFile(pluginConfig) {
fs.writeFileSync(
`./pluginConfig.json`,
jsonStringOfFileContents,
jsonStringOfFileContents + '\n', // Add a newline character at the end
{ flag: 'w+' },
(err) => {
err => {
if (err) {
console.error(err);
return;

View File

@ -1,4 +1,5 @@
import path from 'path';
import os from 'os';
function getPathQuestions(packageType) {
return [
@ -6,7 +7,7 @@ function getPathQuestions(packageType) {
type: 'input',
name: 'name',
message: `What is the name of your ${packageType}?`,
validate: (input) => {
validate: input => {
if (!input) {
return 'Please enter a name';
}
@ -17,8 +18,8 @@ function getPathQuestions(packageType) {
{
type: 'input',
name: 'baseDir',
message: `What is the target absolute path to create your ${packageType} (we recommend you do not use the OHIF ${packageType} folder (./${packageType}s) unless you are developing a core ${packageType}):`,
validate: (input) => {
message: `What is the target path to create your ${packageType} (we recommend you do not use the OHIF ${packageType} folder (./${packageType}s) unless you are developing a core ${packageType}):`,
validate: input => {
if (!input) {
console.log('Please provide a valid target directory path');
return;
@ -26,7 +27,13 @@ function getPathQuestions(packageType) {
return true;
},
filter: (input, answers) => {
return path.resolve(input, answers.name);
// Replace ~ with the user's home directory
const expandedPath = input.replace(/^~(?=$|\/|\\)/, os.homedir());
// Resolve the path to an absolute path
const resolvedPath = path.resolve(expandedPath, answers.name);
return resolvedPath;
},
},
{
@ -43,6 +50,7 @@ function getRepoQuestions(packageType) {
type: 'confirm',
name: 'gitRepository',
message: 'Should it be a git repository?',
default: false,
},
{
type: 'confirm',

View File

@ -1,4 +1,6 @@
import { hotkeys } from '@ohif/core';
import { id } from './id';
import { initToolGroups, toolbarButtons } from '@ohif/mode-longitudinal';
const ohif = {
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
@ -38,12 +40,76 @@ function modeFactory({ modeConfiguration }) {
* Runs when the Mode Route is mounted to the DOM. Usually used to initialize
* Services and other resources.
*/
onModeEnter: ({ servicesManager, extensionManager }) => {},
/**
* Runs when the Mode Route is unmounted from the DOM. Usually used to clean
* up resources and states
*/
onModeExit: () => {},
onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => {
const {
measurementService,
toolbarService,
toolGroupService,
} = servicesManager.services;
measurementService.clearMeasurements();
// Init Default and SR ToolGroups
initToolGroups(extensionManager, toolGroupService, commandsManager);
let unsubscribe;
const activateTool = () => {
toolbarService.recordInteraction({
groupId: 'WindowLevel',
itemId: 'WindowLevel',
interactionType: 'tool',
commands: [
{
commandName: 'setToolActive',
commandOptions: {
toolName: 'WindowLevel',
},
context: 'CORNERSTONE',
},
],
});
// We don't need to reset the active tool whenever a viewport is getting
// added to the toolGroup.
unsubscribe();
};
// Since we only have one viewport for the basic cs3d mode and it has
// only one hanging protocol, we can just use the first viewport
({ unsubscribe } = toolGroupService.subscribe(
toolGroupService.EVENTS.VIEWPORT_ADDED,
activateTool
));
toolbarService.init(extensionManager);
toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [
'MeasurementTools',
'Zoom',
'WindowLevel',
'Pan',
'Capture',
'Layout',
'MPR',
'Crosshairs',
'MoreTools',
]);
},
onModeExit: ({ servicesManager }) => {
const {
toolGroupService,
syncGroupService,
toolbarService,
segmentationService,
cornerstoneViewportService,
} = servicesManager.services;
toolGroupService.destroy();
syncGroupService.destroy();
segmentationService.destroy();
cornerstoneViewportService.destroy();
},
/** */
validationTags: {
study: [],
@ -93,7 +159,7 @@ function modeFactory({ modeConfiguration }) {
/** SopClassHandlers used by the mode */
sopClassHandlers: [ohif.sopClassHandler],
/** hotkeys for mode */
hotkeys: [''],
hotkeys: [...hotkeys.defaults.hotkeyBindings],
};
}

View File

@ -292,7 +292,7 @@ are currently being used by the viewer.
## Private NPM Repos
For the `yarn cli` to view private NPM repos, create a read-only token with the
For the `yarn cli` to view private NPM repos, create a read-only token with the
following steps and export it as an environmental variable. You may also export
an existing npm token.
```
@ -300,3 +300,9 @@ npm login
npm token create --read-only
export NPM_TOKEN=<your readonly token>
```
## External dependencies
The ohif-cli will add the path to the external dependencies to the webpack config,
so that you can install them in your project and use them in your custom
extensions and modes. To achieve this ohif-cli will update the webpack.pwa.js
file in the platform/viewer directory.

View File

@ -127,7 +127,9 @@ window.config = {
> You can simply use the stylings from tailwind CSS in the whiteLabeling
In addition to text, you can also add your custom logo
In addition to text, you can also add your custom logo. You can put them
inside the platform/viewer/public/assets folder and use them in the
whiteLabeling section.
```js
window.config = {
@ -143,7 +145,7 @@ window.config = {
href: '/',
},
React.createElement('img', {
src: './customLogo.svg',
src: './assets/customLogo.svg',
// className: 'w-8 h-8',
})
);

View File

@ -52,7 +52,7 @@ const Thumbnail = ({
'flex flex-1 items-center justify-center rounded-md bg-black text-base text-white overflow-hidden min-h-32',
isActive
? 'border-2 border-primary-light'
: 'border border-secondary-light group-focus:border-blue-300 hover:border-blue-300'
: 'border border-secondary-light hover:border-blue-300'
)}
style={{
margin: isActive ? '0' : '1px',

View File

@ -106,7 +106,7 @@ module.exports = (env, argv) => {
'../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import',
to: DIST_DIR,
globOptions: {
ignore: ['*.js.map'],
ignore: ['**/*.min.js.map'],
},
},
// Copy dicom-image-loader build files

View File

@ -1,6 +1,5 @@
window.config = {
routerBasename: '/',
// whiteLabelling: {},
extensions: [],
modes: [],
showStudyList: true,
@ -11,6 +10,7 @@ window.config = {
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
@ -54,26 +54,6 @@ window.config = {
// 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',

View File

@ -24,6 +24,7 @@ window.config = {
prefetch: 25,
},
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
@ -96,13 +97,12 @@ window.config = {
// },
// React.createElement('img',
// {
// src: './customLogo.svg',
// src: './assets/customLogo.svg',
// className: 'w-8 h-8',
// }
// ))
// },
// },
defaultDataSourceName: 'dicomweb',
hotkeys: [
{
commandName: 'incrementActiveViewport',

View File

@ -8,6 +8,7 @@ window.config = {
showWarningMessageForCrossOrigin: true,
strictZSpacingForVolumeViewport: true,
showCPUFallbackMessage: true,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'DCM4CHEE Server',
@ -25,7 +26,6 @@ window.config = {
},
},
],
defaultDataSourceName: 'dicomweb',
hotkeys: [
{
commandName: 'incrementActiveViewport',

View File

@ -1,6 +1,5 @@
window.config = {
routerBasename: '/',
// whiteLabelling: {},
extensions: [],
modes: [],
showStudyList: true,
@ -11,6 +10,7 @@ window.config = {
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
@ -45,5 +45,4 @@ window.config = {
configuration: {},
},
],
defaultDataSourceName: 'dicomweb',
};

View File

@ -1,6 +1,5 @@
window.config = {
routerBasename: '/',
// whiteLabelling: {},
extensions: [],
modes: [],
showStudyList: true,
@ -12,6 +11,7 @@ window.config = {
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'Static WADO Local Data',
@ -55,26 +55,6 @@ window.config = {
// 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',

View File

@ -9,6 +9,7 @@ window.config = {
showCPUFallbackMessage: true,
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'Orthanc Server',
@ -39,5 +40,4 @@ window.config = {
configuration: {},
},
],
defaultDataSourceName: 'dicomweb',
};

View File

@ -9,6 +9,7 @@ window.config = {
showCPUFallbackMessage: true,
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'Orthanc Server',
@ -39,5 +40,4 @@ window.config = {
configuration: {},
},
],
defaultDataSourceName: 'dicomweb',
};

View File

@ -10,6 +10,7 @@ window.config = {
showCPUFallbackMessage: false,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'e2e',
dataSources: [
{
friendlyName: 'StaticWado test data',
@ -118,25 +119,5 @@ window.config = {
// 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: 'e2e',
hotkeys: [],
};

View File

@ -30,11 +30,11 @@ window.config = {
revokeAccessTokenOnSignout: true,
},
],
// whiteLabelling: {},
extensions: [],
modes: [],
showStudyList: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
@ -72,5 +72,4 @@ window.config = {
configuration: {},
},
],
defaultDataSourceName: 'dicomweb',
};

View File

@ -13,6 +13,7 @@ window.config = {
showCPUFallbackMessage: true,
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'DCM4CHEE Server',
@ -49,5 +50,4 @@ window.config = {
},
],
studyListFunctionsEnabled: true,
defaultDataSourceName: 'dicomweb',
};

View File

@ -1,6 +1,5 @@
window.config = {
routerBasename: '/',
// whiteLabelling: {},
extensions: [],
modes: [],
customizationService: {
@ -14,6 +13,7 @@ window.config = {
showCPUFallbackMessage: true,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
@ -57,26 +57,6 @@ window.config = {
// 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',

View File

@ -14,6 +14,7 @@ window.config = {
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'Static WADO Local Data',
@ -56,7 +57,6 @@ window.config = {
// Could use services manager here to bring up a dialog/modal if needed.
console.warn('test, navigate to https://ohif.org/');
},
defaultDataSourceName: 'dicomweb',
hotkeys: [
{
commandName: 'incrementActiveViewport',

View File

@ -22,6 +22,7 @@ window.config = {
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'default',
dataSources: [
{
friendlyName: 'Static WADO Local Data',
@ -131,7 +132,6 @@ window.config = {
// Could use services manager here to bring up a dialog/modal if needed.
console.warn('test, navigate to https://ohif.org/');
},
defaultDataSourceName: 'default',
// Only list the unique hotkeys
hotkeys: [],

View File

@ -1,6 +1,5 @@
window.config = {
routerBasename: '/',
// whiteLabelling: {},
extensions: [],
modes: [],
showStudyList: true,
@ -11,6 +10,7 @@ window.config = {
showLoadingIndicator: true,
strictZSpacingForVolumeViewport: true,
// filterQueryParam: false,
defaultDataSourceName: 'dicomweb',
dataSources: [
{
friendlyName: 'dcmjs DICOMWeb Server',
@ -54,26 +54,6 @@ window.config = {
// 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',