feat(cli): Ohif cli for modes and extensions modification (#2696)

* feat: Add initial cli tool structure

* feat: add copying template files

* feat: Add mode template and command

* feat: Add readme template generation

* feat: Add documentation to extension template

* feat: Enhance documentation of the template mode

* fix: cli module type

* feat: Add config-based mode and extension registration (#2660)

* feat: Add ohif cli add/remove extension/mode (#2661)

* Basic working CLI for add-extension and remove-extension

* Basic cli for add/remove extension/mode, lots more to do.

* Cleanup and harden]

* feat: Add list of tasks to add-mode

Co-authored-by: Alireza <ar.sedghi@gmail.com>

* feat: Add git initialization for the mode or extension template (#2662)

* fix: package json file to include templates

* feat: Add git initialization for the mode or extension template

* feat: Add more checks of git and target dir

* feat: refactore library utilities

* feat: Add the list command to print extensions and modes (#2664)

* feat: Add the list command to print extensions and modes

* Add todo

* Feat/ohif cli validation + auto install (#2671)

* WIP

* Working mode keyword verification

* Validation

* auto install extensions based on modes

* WIP remove unused extensions on removeMove

* Working add-mode, remove-mode automatic extension management.

* If extension is in used by a mode, don't allow the CLI to uninstall it

* Cleanup addExtension

* cleanup removeExtension and addMode

* Cleanup removeMode

* Update existing extensions with the needed keywords/peer deps

* Fix broken config

* Feat/cli search (#2677)

* feat: refactor pretty print for console

* feat: add search for modes and extensions

* fix: ugly colors

* Feat/ohif cli error handling publishing (#2679)

* WIP

* fix: webpack imports

* wip

* fix: react router dom private routes

* from last commit

* wip

* fix: webpack prod builds

* WIP

* Working regsitration with new IDs

* Stable

Co-authored-by: Alireza <ar.sedghi@gmail.com>

* verify extensions when constructing modes. (#2681)

* verify extensions when constructing modes.

* Add version to unit tests so it conforms to schema

* Update ohif utils exposed via @ohif/core

* Fix import

* fix tests

* feat: ohif-cli link local modes/extensions for development (#2682)

* feat: enable cli to work with project root

* feat: add initial link package

* feat: add link and unlink extension

* feat: add link and unlink mode

* erro handling for link-package

* feat: add comment on ohif-cli linking for development (#2686)

* Docs/ohif cli (#2687)

* feat: Add documentation for templates

* feat: Add more documentation

* Fix/core publish (#2685)

* versions

* wip

* remove webpack clean output

* fix publish

* use next as dist tag for v3 for now

* fix webpack pro recipe for output

* fix: lerna publish next

* fix(cli): fix issues when trying to link an extension or a mode (#2725)

The generated package.json doesn't contain keywords property which is
required by the linkPackage function. The module apth wasn't correclty
handled too, and when there is no pluginOptions, it fails while reading
the file or while generating a default configuration.

* make dicom pdf and video work after cli merge

* add axios dependency

* comment out the chdir for now

* create id and version based on user inputs

* customizable path for extension and modes

* fix template to make the template mode load

* fix the questions to loop if path is not desirable

* fix templates

* correct package json order

* unify the package creation for extension and mode

* bump versions for each package

* bump extension versions to 3.0

* add gitignore to templates

* fix version requirements when ^

* update docs

* update docs and fix tests

* try to fix the tests

* bump node version

* remove the version from extensions

* remove the version from modes

* remove version from extensionManager

* fix eslint

* revert husky version

* fix eslint

* fix node version for new eslint

* fix documentatoin removing version

* fix cicle ci image version

* fix circle ci node image

* fix circle ci node image

* add back the video and pdf

Co-authored-by: Matthis Duclos <matthis.duclos@gmail.com>
Co-authored-by: James A. Petts <jamesapetts@gmail.com>
This commit is contained in:
Alireza 2022-04-06 13:28:42 -04:00 committed by GitHub
parent 82df3eac03
commit 7f0ad6f84d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
181 changed files with 5985 additions and 3689 deletions

View File

@ -24,7 +24,7 @@ executors:
chrome-and-pacs:
docker:
# Primary container image where all steps run.
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
- image: 'cypress/browsers:node14.17.0-chrome88-ff89'
defaults: &defaults
docker:
@ -181,7 +181,7 @@ jobs:
DEPLOY_TO_DEV:
docker:
- image: circleci/node:14.15.0
- image: circleci/node:14.17.0
environment:
TERM: xterm
NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77
@ -196,7 +196,7 @@ jobs:
DEPLOY_TO_STAGING:
docker:
- image: circleci/node:14.15.0
- image: circleci/node:14.17.0
environment:
TERM: xterm
NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969
@ -211,7 +211,7 @@ jobs:
DEPLOY_TO_PRODUCTION:
docker:
- image: circleci/node:14.15.0
- image: circleci/node:14.17.0
environment:
TERM: xterm
NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0
@ -263,7 +263,7 @@ jobs:
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
- run: npx lerna version
- run: yarn run build:package-all
- run: npx lerna publish from-package
- run: yarn run lerna:publish
- run: npx lerna run ci:generateSuccessVersion --stream
- persist_to_workspace:
root: ~/repo
@ -367,12 +367,13 @@ workflows:
executor: cypress/browsers-chrome76
browser: chrome
pre-steps:
- run: 'rm -rf ~/.yarn && yarn -v && yarn global
add wait-on'
- run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on'
yarn: true
store_artifacts: false
working_directory: platform/viewer
build: yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build
build:
yarn test:data && npx cross-env QUICK_BUILD=true
APP_CONFIG=config/dicomweb-server.js yarn run build
# start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
@ -434,7 +435,9 @@ workflows:
yarn: true
store_artifacts: false
working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js yarn run build
build:
npx cross-env QUICK_BUILD=true APP_CONFIG=config/e2e.js yarn run
build
# start server --> verify running --> percy + chrome + cypress
command: yarn run test:e2e:dist
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'

4
.eslintignore Normal file
View File

@ -0,0 +1,4 @@
config/**
docs/**
img/**
node_modules

View File

@ -12,12 +12,7 @@
"settings": {
"react": {
"version": "detect"
},
"rules": {
"react/jsx-props-no-spreading": "error",
"react-hooks/exhaustive-deps": "false",
"import/no-unused-modules": [1, {"unusedExports": true}]
}
},
"globals": {
"cy": true,

3
.gitignore vendored
View File

@ -36,3 +36,6 @@ screenshots/
# Locize settings
.locize
# autogenerated files
platform/viewer/src/pluginImports.js

View File

@ -2,7 +2,7 @@
"name": "root",
"private": true,
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.16.0"
},

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
*.md

View File

@ -1,7 +1,9 @@
const autoprefixer = require('autoprefixer');
const path = require('path');
const tailwindcss = require('tailwindcss');
const tailwindConfigPath = path.resolve('tailwind.config.js');
const tailwindConfigPath = path.resolve(
'../../platform/viewer/tailwind.config.js'
);
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';

View File

@ -3,17 +3,20 @@ const dotenv = require('dotenv');
//
const path = require('path');
const webpack = require('webpack');
// ~~ PLUGINS
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin;
const TerserJSPlugin = require('terser-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
// ~~ PackageJSON
const PACKAGE = require('../platform/viewer/package.json');
// ~~ RULES
const loadShadersRule = require('./rules/loadShaders.js');
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
const transpileJavaScriptRule = require('./rules/transpileJavaScript.js');
const cssToJavaScript = require('./rules/cssToJavaScript.js');
// ~~ PLUGINS
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin;
const TerserJSPlugin = require('terser-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
// ~~ ENV VARS
const NODE_ENV = process.env.NODE_ENV;
@ -39,16 +42,16 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
app: `${SRC_DIR}/index.js`,
},
optimization: {
splitChunks: {
// include all types of chunks
chunks: 'all',
},
// splitChunks: {
// // include all types of chunks
// chunks: 'all',
// },
//runtimeChunk: 'single',
minimize: isProdBuild,
sideEffects: true,
},
output: {
clean: true,
// clean: true,
publicPath: '/',
},
context: SRC_DIR,
@ -98,6 +101,8 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
path.resolve(__dirname, '../node_modules'),
// Hoisted Yarn Workspace Modules
path.resolve(__dirname, '../../../node_modules'),
path.resolve(__dirname, '../platform/viewer/node_modules'),
path.resolve(__dirname, '../platform/ui/node_modules'),
SRC_DIR,
],
// Attempt to resolve these extensions in order.
@ -131,15 +136,6 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
}),
// Uncomment to generate bundle analyzer
// new BundleAnalyzerPlugin(),
new CopyPlugin({
patterns: [
{
from:
'../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
to: DIST_DIR,
},
],
}),
],
};

View File

@ -5,7 +5,7 @@ import { IWebApiDataSource } from '@ohif/core';
*
*/
export default {
id: 'org.ohif.*',
id: '@ohif/extension-*',
/**
* LIFECYCLE HOOKS

View File

@ -1,5 +1,5 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');

View File

@ -1,8 +1,9 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const pkg = require('./../package.json');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
@ -39,6 +40,10 @@ module.exports = (env, argv) => {
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new MiniCssExtractPlugin({
filename: './dist/[name].css',
chunkFilename: './dist/[id].css',
}),
],
});
};

View File

@ -8,7 +8,7 @@
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.16.0"
},
@ -16,6 +16,9 @@
"dist",
"README.md"
],
"keywords": [
"ohif-extension"
],
"publishConfig": {
"access": "public"
},
@ -29,8 +32,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"@ohif/ui": "^0.50.0",
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
"cornerstone-core": "2.6.0",
"cornerstone-math": "0.1.9",
"cornerstone-tools": "6.0.2",

View File

@ -1,3 +1,5 @@
const id = 'org.ohif.cornerstone';
import packageJson from '../package.json';
export default id;
const id = packageJson.name;
export { id };

View File

@ -1,7 +1,8 @@
import React from 'react';
import init from './init.js';
import commandsModule from './commandsModule.js';
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
import { id } from './id.js';
// import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
const Component = React.lazy(() => {
return import(/* webpackPrefetch: true */ './OHIFCornerstoneViewport');
@ -22,8 +23,7 @@ export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'org.ohif.cornerstone',
id,
/**
*
*
@ -59,5 +59,3 @@ export default {
return commandsModule({ servicesManager, commandsManager });
},
};
export { CornerstoneViewportDownloadForm };

View File

@ -1,5 +1,5 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');

View File

@ -1,10 +1,11 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ROOT_DIR = path.join(__dirname, './..');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './../');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
@ -30,7 +31,7 @@ module.exports = (env, argv) => {
},
output: {
path: ROOT_DIR,
library: 'OHIFExtDefault',
library: 'OHIFExtCornerstone',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
@ -39,6 +40,10 @@ module.exports = (env, argv) => {
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new MiniCssExtractPlugin({
filename: './dist/[name].css',
chunkFilename: './dist/[id].css',
}),
],
});
};

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-default",
"version": "1.0.1",
"version": "3.0.0",
"description": "Common/default features and functionality for basic image viewing",
"author": "OHIF Core Team",
"license": "MIT",
@ -11,7 +11,7 @@
"access": "public"
},
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.18.0"
},
@ -19,6 +19,9 @@
"dist",
"README.md"
],
"keywords": [
"ohif-extension"
],
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:dicom-pdf": "yarn run dev",
@ -27,16 +30,14 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"@ohif/i18n": "^0.52.8",
"@ohif/core": "^3.0.0",
"@ohif/i18n": "^1.0.0",
"dcmjs": "0.16.1",
"dicomweb-client": "^0.6.0",
"prop-types": "^15.6.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^10.11.0",
"react-router": "next",
"react-router-dom": "next",
"webpack": "^5.50.0",
"webpack-merge": "^5.7.3"
},

View File

@ -165,18 +165,22 @@ function PanelStudyBrowser({
);
const updatedExpandedStudyInstanceUIDs = shouldCollapseStudy
? // eslint-disable-next-line prettier/prettier
[
...expandedStudyInstanceUIDs.filter(
stdyUid => stdyUid !== StudyInstanceUID
),
]
[
...expandedStudyInstanceUIDs.filter(
stdyUid => stdyUid !== StudyInstanceUID
),
]
: [...expandedStudyInstanceUIDs, StudyInstanceUID];
setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs);
if (!shouldCollapseStudy) {
const madeInClient = true
requestDisplaySetCreationForStudy(DisplaySetService, StudyInstanceUID, madeInClient);
const madeInClient = true;
requestDisplaySetCreationForStudy(
DisplaySetService,
StudyInstanceUID,
madeInClient
);
}
}

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
SidePanel,
@ -12,12 +13,10 @@ import {
import i18n from '@ohif/i18n';
import { hotkeys } from '@ohif/core';
import { useNavigate } from 'react-router-dom';
import { useAppConfig } from '@state';
const { availableLanguages, defaultLanguage, currentLanguage } = i18n;
import { useAppConfig } from '@state';
function Toolbar({ servicesManager }) {
const { ToolBarService } = servicesManager.services;
const [toolbarButtons, setToolbarButtons] = useState([]);
@ -94,7 +93,7 @@ function ViewerLayout({
const navigate = useNavigate();
const onClickReturnButton = () => {
navigate('/')
navigate('/');
};
const { t } = useTranslation();
@ -108,7 +107,12 @@ function ViewerLayout({
{
title: t('Header:About'),
icon: 'info',
onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer', contentProps: { versionNumber, buildNumber } }),
onClick: () =>
show({
content: AboutModal,
title: 'About OHIF Viewer',
contentProps: { versionNumber, buildNumber },
}),
},
{
title: t('Header:Preferences'),
@ -136,7 +140,7 @@ function ViewerLayout({
hide();
},
onReset: () => hotkeysManager.restoreDefaultBindings(),
hotkeysModule: hotkeys
hotkeysModule: hotkeys,
},
}),
},
@ -185,7 +189,11 @@ function ViewerLayout({
return (
<div>
<Header menuOptions={menuOptions} onClickReturnButton={onClickReturnButton} WhiteLabeling={appConfig.whiteLabeling} >
<Header
menuOptions={menuOptions}
onClickReturnButton={onClickReturnButton}
WhiteLabeling={appConfig.whiteLabeling}
>
<ErrorBoundary context="Primary Toolbar">
<div className="relative flex justify-center">
<Toolbar servicesManager={servicesManager} />
@ -193,7 +201,7 @@ function ViewerLayout({
</ErrorBoundary>
</Header>
<div
className="flex flex-row flex-nowrap items-stretch w-full overflow-hidden"
className="flex flex-row items-stretch w-full overflow-hidden flex-nowrap"
style={{ height: 'calc(100vh - 52px' }}
>
{/* LEFT SIDEPANELS */}

View File

@ -1,7 +1,7 @@
import { isImage } from '@ohif/core/src/utils/isImage';
import ImageSet from '@ohif/core/src/classes/ImageSet';
import isDisplaySetReconstructable from '@ohif/core/src/utils/isDisplaySetReconstructable';
import id from './id';
import { id } from './id';
const sopClassHandlerName = 'stack';

View File

@ -1,3 +1,5 @@
const id = 'org.ohif.default';
import packageJson from '../package.json';
export default id;
const id = packageJson.name;
export { id };

View File

@ -5,7 +5,7 @@ import getSopClassHandlerModule from './getSopClassHandlerModule.js';
import getHangingProtocolModule from './getHangingProtocolModule.js';
import getToolbarModule from './getToolbarModule.js';
import commandsModule from './commandsModule';
import id from './id';
import { id } from './id.js';
export default {
/**

View File

@ -8,7 +8,7 @@
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.16.0"
},
@ -29,8 +29,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"@ohif/ui": "^0.50.0",
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
"cornerstone-core": "^2.6.0",
"cornerstone-math": "^0.1.9",
"cornerstone-tools": "6.0.2",

View File

@ -1,4 +1,4 @@
import { Name, SOPClassHandlerId } from './id';
import { SOPClassHandlerId } from './id';
import { utils, classes } from '@ohif/core';
const { ImageSet } = classes;
@ -9,49 +9,67 @@ const SOP_CLASS_UIDS = {
const sopClassUids = Object.values(SOP_CLASS_UIDS);
const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) => {
const _getDisplaySetsFromSeries = (
instances,
servicesManager,
extensionManager
) => {
const dataSource = extensionManager.getActiveDataSource()[0];
return instances
.map(instance => {
const { Modality, SOPInstanceUID, EncapsulatedDocument } = instance;
const { SeriesDescription = "PDF", MIMETypeOfEncapsulatedDocument, } = instance;
const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, } = instance;
const pdfUrl = dataSource.retrieve.directURL({
instance,
tag: 'EncapsulatedDocument',
defaultType: MIMETypeOfEncapsulatedDocument || "application/pdf",
singlepart: "pdf",
});
const displaySet = {
//plugin: id,
Modality,
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
SeriesDate,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SOPClassHandlerId,
referencedImages: null,
measurements: null,
pdfUrl,
others: [instance],
thumbnailSrc: dataSource.retrieve.directURL({ instance, defaultPath: "/thumbnail", defaultType: "image/jpeg", tag: "Absent" }),
isDerivedDisplaySet: true,
isLoaded: false,
sopClassUids,
numImageFrames: 0,
numInstances: 1,
instance,
};
return displaySet;
return instances.map(instance => {
const { Modality, SOPInstanceUID, EncapsulatedDocument } = instance;
const {
SeriesDescription = 'PDF',
MIMETypeOfEncapsulatedDocument,
} = instance;
const {
SeriesNumber,
SeriesDate,
SeriesInstanceUID,
StudyInstanceUID,
} = instance;
const pdfUrl = dataSource.retrieve.directURL({
instance,
tag: 'EncapsulatedDocument',
defaultType: MIMETypeOfEncapsulatedDocument || 'application/pdf',
singlepart: 'pdf',
});
const displaySet = {
//plugin: id,
Modality,
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
SeriesDate,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SOPClassHandlerId,
referencedImages: null,
measurements: null,
pdfUrl,
others: [instance],
thumbnailSrc: dataSource.retrieve.directURL({
instance,
defaultPath: '/thumbnail',
defaultType: 'image/jpeg',
tag: 'Absent',
}),
isDerivedDisplaySet: true,
isLoaded: false,
sopClassUids,
numImageFrames: 0,
numInstances: 1,
instance,
};
return displaySet;
});
};
export default function getSopClassHandlerModule({ servicesManager, extensionManager }) {
export default function getSopClassHandlerModule({
servicesManager,
extensionManager,
}) {
const getDisplaySetsFromSeries = instances => {
return _getDisplaySetsFromSeries(
instances,
@ -62,7 +80,7 @@ export default function getSopClassHandlerModule({ servicesManager, extensionMan
return [
{
name: Name,
name: 'dicom-pdf',
sopClassUids,
getDisplaySetsFromSeries,
},

View File

@ -1,8 +1,6 @@
const Name = 'dicom-pdf';
const id = `org.ohif.${Name}`;
import packageJson from '../package.json';
export default id;
const id = packageJson.name;
const SOPClassHandlerId = `${id}.sopClassHandlerModule.dicom-pdf`;
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${Name}`;
export { Name, SOPClassHandlerId, };
export { id, SOPClassHandlerId };

View File

@ -1,6 +1,6 @@
import React from 'react';
import getSopClassHandlerModule from './getSopClassHandlerModule';
import id from './id.js';
import { id } from './id.js';
const Component = React.lazy(() => {
return import(
@ -24,23 +24,6 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id,
dependencies: [
// TODO -> This isn't used anywhere yet, but we do have a hard dependency, and need to check for these in the future.
// OHIF-229
{
id: 'org.ohif.cornerstone',
version: '3.0.0',
},
{
id: 'org.ohif.measurement-tracking',
version: '^0.0.1',
},
],
preRegistration({ servicesManager, configuration = {} }) {
// No-op for now
},
/**
*
*
@ -58,7 +41,9 @@ export default {
);
};
return [{ name: 'dicom-pdf', component: ExtendedOHIFCornerstonePdfViewport }];
return [
{ name: 'dicom-pdf', component: ExtendedOHIFCornerstonePdfViewport },
];
},
getCommandsModule({ servicesManager }) {
return {

View File

@ -1,5 +1,5 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');

View File

@ -1,10 +1,12 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ROOT_DIR = path.join(__dirname, './..');
const pkg = require('./../package.json');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const ROOT_DIR = path.join(__dirname, './../');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
@ -30,7 +32,7 @@ module.exports = (env, argv) => {
},
output: {
path: ROOT_DIR,
library: 'OHIFExtDICOMSR',
library: 'OHIFExtCornerstone',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
@ -39,6 +41,10 @@ module.exports = (env, argv) => {
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new MiniCssExtractPlugin({
filename: './dist/[name].css',
chunkFilename: './dist/[id].css',
}),
],
});
};

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-sr",
"version": "0.0.1",
"version": "3.0.0",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
@ -8,7 +8,7 @@
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.16.0"
},
@ -19,6 +19,9 @@
"publishConfig": {
"access": "public"
},
"keywords": [
"ohif-extension"
],
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:cornerstone": "yarn run dev",
@ -29,8 +32,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"@ohif/ui": "^0.50.0",
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
"cornerstone-core": "^2.6.0",
"cornerstone-math": "^0.1.9",
"cornerstone-tools": "6.0.2",
@ -40,7 +43,9 @@
"hammerjs": "^2.0.8",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-cornerstone-viewport": "4.1.2"
"react-cornerstone-viewport": "4.1.2",
"@ohif/extension-cornerstone": "^3.0.0",
"@ohif/extension-measurement-tracking": "^3.0.0"
},
"dependencies": {
"@babel/runtime": "7.16.3",

View File

@ -1,7 +1,8 @@
const id = 'org.ohif.dicom-sr';
import packageJson from '../package.json';
export default id;
const id = packageJson.name;
const SOPClassHandlerName = 'dicom-sr';
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
export { SOPClassHandlerName, SOPClassHandlerId };
export { SOPClassHandlerName, SOPClassHandlerId, id };

View File

@ -1,8 +1,8 @@
import React from 'react';
import getSopClassHandlerModule from './getSopClassHandlerModule';
import onModeEnter from './onModeEnter';
import id from './id.js';
import init from './init';
import { id } from './id.js';
const Component = React.lazy(() => {
return import(
@ -26,18 +26,6 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id,
dependencies: [
// TODO -> This isn't used anywhere yet, but we do have a hard dependency, and need to check for these in the future.
// OHIF-229
{
id: 'org.ohif.cornerstone',
version: '3.0.0',
},
{
id: 'org.ohif.measurement-tracking',
version: '^0.0.1',
},
],
preRegistration({ servicesManager, configuration = {} }) {
init({ servicesManager, configuration });

View File

@ -1,6 +1,6 @@
import cornerstoneTools from 'cornerstone-tools';
import dicomSRModule from './tools/modules/dicomSRModule';
import id from './id';
import { id } from './id';
import TOOL_NAMES from './constants/toolNames';
@ -14,9 +14,9 @@ const defaultConfig = {
* @param {object} configuration
*/
export default function init({ configuration = {} }) {
const conifg = Object.assign({}, defaultConfig, configuration);
const config = Object.assign({}, defaultConfig, configuration);
TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = conifg.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = config.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
cornerstoneTools.register('module', id, dicomSRModule);
}

View File

@ -3,7 +3,7 @@ import { pixelToCanvas } from 'cornerstone-core';
import TOOL_NAMES from '../constants/toolNames';
import SCOORD_TYPES from '../constants/scoordTypes';
import id from '../id';
import { id } from '../id';
// Cornerstone 3rd party dev kit imports
const draw = importInternal('drawing/draw');

View File

@ -14,7 +14,7 @@ import {
} from '@ohif/ui';
import TOOL_NAMES from './../constants/toolNames';
import { adapters } from 'dcmjs';
import id from './../id';
import { id } from './../id';
const { formatDate } = utils;
const scrollToIndex = cornerstoneTools.importInternal('util/scrollToIndex');
@ -23,7 +23,8 @@ const globalImageIdSpecificToolStateManager =
const { StackManager, guid } = OHIF.utils;
const MEASUREMENT_TRACKING_EXTENSION_ID = 'org.ohif.measurement-tracking';
const MEASUREMENT_TRACKING_EXTENSION_ID =
'@ohif/extension-measurement-tracking';
function OHIFCornerstoneSRViewport({
children,
@ -81,7 +82,7 @@ function OHIFCornerstoneSRViewport({
)
) {
const contextModule = extensionManager.getModuleEntry(
'org.ohif.measurement-tracking.contextModule.TrackedMeasurementsContext'
'@ohif/extension-measurement-tracking.contextModule.TrackedMeasurementsContext'
);
const useTrackedMeasurements = () => useContext(contextModule.context);

View File

@ -8,7 +8,7 @@
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.16.0"
},
@ -30,7 +30,7 @@
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"@ohif/ui": "^0.50.0",
"@ohif/ui": "^2.0.0",
"cornerstone-core": "^2.6.0",
"cornerstone-math": "^0.1.9",
"cornerstone-tools": "6.0.2",

View File

@ -1,5 +1,5 @@
import { Name, SOPClassHandlerId } from './id';
import { utils, } from '@ohif/core';
import { SOPClassHandlerId } from './id';
import { utils } from '@ohif/core';
const SOP_CLASS_UIDS = {
VIDEO_MICROSCOPIC_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.2.1',
@ -25,17 +25,33 @@ const SupportedTransferSyntaxes = {
const supportedTransferSyntaxUIDs = Object.values(SupportedTransferSyntaxes);
const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) => {
const _getDisplaySetsFromSeries = (
instances,
servicesManager,
extensionManager
) => {
const dataSource = extensionManager.getActiveDataSource()[0];
return instances
.filter(metadata => {
const tsuid = metadata.AvailableTransferSyntaxUID ||
metadata.TransferSyntaxUID || metadata['00083002'];
const tsuid =
metadata.AvailableTransferSyntaxUID ||
metadata.TransferSyntaxUID ||
metadata['00083002'];
return supportedTransferSyntaxUIDs.includes(tsuid);
})
.map(instance => {
const { Modality, SOPInstanceUID, SeriesDescription = "VIDEO" } = instance;
const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, NumberOfFrames } = instance;
const {
Modality,
SOPInstanceUID,
SeriesDescription = 'VIDEO',
} = instance;
const {
SeriesNumber,
SeriesDate,
SeriesInstanceUID,
StudyInstanceUID,
NumberOfFrames,
} = instance;
const displaySet = {
//plugin: id,
Modality,
@ -49,9 +65,18 @@ const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager)
SOPClassHandlerId,
referencedImages: null,
measurements: null,
videoUrl: dataSource.retrieve.directURL({ instance, singlepart: "video", tag: "PixelData", }),
videoUrl: dataSource.retrieve.directURL({
instance,
singlepart: 'video',
tag: 'PixelData',
}),
others: [instance],
thumbnailSrc: dataSource.retrieve.directURL({ instance, defaultPath: "/thumbnail", defaultType: "image/jpeg", tag: "Absent" }),
thumbnailSrc: dataSource.retrieve.directURL({
instance,
defaultPath: '/thumbnail',
defaultType: 'image/jpeg',
tag: 'Absent',
}),
isDerivedDisplaySet: true,
isLoaded: false,
sopClassUids,
@ -62,7 +87,10 @@ const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager)
});
};
export default function getSopClassHandlerModule({ servicesManager, extensionManager }) {
export default function getSopClassHandlerModule({
servicesManager,
extensionManager,
}) {
const getDisplaySetsFromSeries = instances => {
return _getDisplaySetsFromSeries(
instances,
@ -73,7 +101,7 @@ export default function getSopClassHandlerModule({ servicesManager, extensionMan
return [
{
name: Name,
name: 'dicom-video',
sopClassUids,
getDisplaySetsFromSeries,
},

View File

@ -1,8 +1,6 @@
const Name = 'dicom-video';
const id = `org.ohif.${Name}`;
import packageJson from '../package.json';
export default id;
const id = packageJson.name;
const SOPClassHandlerId = `${id}.sopClassHandlerModule.dicom-video`;
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${Name}`;
export { Name, SOPClassHandlerId, };
export { SOPClassHandlerId, id };

View File

@ -1,6 +1,6 @@
import React from 'react';
import getSopClassHandlerModule from './getSopClassHandlerModule';
import id from './id.js';
import { id } from './id';
const Component = React.lazy(() => {
return import(
@ -24,22 +24,6 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id,
dependencies: [
// TODO -> This isn't used anywhere yet, but we do have a hard dependency, and need to check for these in the future.
// OHIF-229
{
id: 'org.ohif.cornerstone',
version: '3.0.0',
},
{
id: 'org.ohif.measurement-tracking',
version: '^0.0.1',
},
],
preRegistration({ servicesManager, configuration = {} }) {
// No-op for now
},
/**
*
@ -58,7 +42,9 @@ export default {
);
};
return [{ name: 'dicom-video', component: ExtendedOHIFCornerstoneVideoViewport }];
return [
{ name: 'dicom-video', component: ExtendedOHIFCornerstoneVideoViewport },
];
},
getCommandsModule({ servicesManager }) {
return {

View File

@ -1,5 +1,5 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');

View File

@ -1,10 +1,12 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ROOT_DIR = path.join(__dirname, './..');
const pkg = require('./../package.json');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const ROOT_DIR = path.join(__dirname, './../');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
@ -30,7 +32,7 @@ module.exports = (env, argv) => {
},
output: {
path: ROOT_DIR,
library: 'OHIFExtDefault',
library: 'OHIFExtCornerstone',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
@ -39,6 +41,10 @@ module.exports = (env, argv) => {
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new MiniCssExtractPlugin({
filename: './dist/[name].css',
chunkFilename: './dist/[id].css',
}),
],
});
};

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/extension-measurement-tracking",
"version": "0.0.1",
"version": "3.0.0",
"description": "Tracking features and functionality for basic image viewing",
"author": "OHIF Core Team",
"license": "MIT",
@ -11,7 +11,7 @@
"access": "public"
},
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.18.0"
},
@ -19,6 +19,9 @@
"dist",
"README.md"
],
"keywords": [
"ohif-extension"
],
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:dicom-pdf": "yarn run dev",
@ -27,21 +30,22 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"@ohif/core": "^3.0.0",
"classnames": "^2.2.6",
"cornerstone-core": "^2.6.0",
"cornerstone-tools": "6.0.2",
"dcmjs": "0.16.1",
"prop-types": "^15.6.2",
"react": "^16.13.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-cornerstone-viewport": "^4.1.2",
"react-dom": "^16.13.1",
"webpack": "^5.50.0",
"webpack-merge": "^5.7.3"
"webpack-merge": "^5.7.3",
"@ohif/ui": "^2.0.0"
},
"dependencies": {
"@babel/runtime": "7.16.3",
"@ohif/ui": "^1.8.2",
"@ohif/ui": "^2.0.0",
"@xstate/react": "^0.8.1",
"xstate": "^4.10.0"
}

View File

@ -17,7 +17,8 @@ const TrackedMeasurementsContext = React.createContext();
TrackedMeasurementsContext.displayName = 'TrackedMeasurementsContext';
const useTrackedMeasurements = () => useContext(TrackedMeasurementsContext);
const SR_SOPCLASSHANDLERID = "org.ohif.dicom-sr.sopClassHandlerModule.dicom-sr";
const SR_SOPCLASSHANDLERID =
'@ohif/extension-dicom-sr.sopClassHandlerModule.dicom-sr';
/**
*
@ -33,7 +34,10 @@ function TrackedMeasurementsContextProvider(
const machineOptions = Object.assign({}, defaultOptions);
machineOptions.actions = Object.assign({}, machineOptions.actions, {
jumpToFirstMeasurementInActiveViewport: (ctx, evt) => {
const { DisplaySetService, MeasurementService } = servicesManager.services;
const {
DisplaySetService,
MeasurementService,
} = servicesManager.services;
const { trackedStudy, trackedSeries } = ctx;
const measurements = MeasurementService.getMeasurements();
const trackedMeasurements = measurements.filter(
@ -44,7 +48,10 @@ function TrackedMeasurementsContextProvider(
const id = trackedMeasurements[0].id;
MeasurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, id);
MeasurementService.jumpToMeasurement(
viewportGrid.activeViewportIndex,
id
);
},
showStructuredReportDisplaySetInActiveViewport: (ctx, evt) => {
if (evt.data.createdDisplaySetInstanceUIDs.length > 0) {
@ -98,7 +105,7 @@ function TrackedMeasurementsContextProvider(
}),
promptHydrateStructuredReport: promptHydrateStructuredReport.bind(null, {
servicesManager,
extensionManager
extensionManager,
}),
});
@ -149,9 +156,11 @@ function TrackedMeasurementsContextProvider(
// The issue here is that this handler in TrackedMeasurementsContext
// ends up occurring before the Viewport is created, so the displaySet
// is not loaded yet, and isRehydratable is undefined unless we call load().
if (displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID &&
!displaySet.isLoaded &&
displaySet.load) {
if (
displaySet.SOPClassHandlerId === SR_SOPCLASSHANDLERID &&
!displaySet.isLoaded &&
displaySet.load
) {
displaySet.load();
}

View File

@ -0,0 +1,5 @@
import packageJson from '../package.json';
const id = packageJson.name;
export { id };

View File

@ -1,12 +1,14 @@
import getContextModule from './getContextModule.js';
import getPanelModule from './getPanelModule.js';
import getViewportModule from './getViewportModule.js';
import { id } from './id.js';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'org.ohif.measurement-tracking',
id,
getContextModule,
getPanelModule,
getViewportModule,

View File

@ -228,7 +228,7 @@ function TrackedCornerstoneViewport(props) {
const renderViewport = () => {
const { component: Component } = extensionManager.getModuleEntry(
'org.ohif.cornerstone.viewportModule.cornerstone'
'@ohif/extension-cornerstone.viewportModule.cornerstone'
);
return (
<Component
@ -257,7 +257,7 @@ function TrackedCornerstoneViewport(props) {
isLocked: false,
isRehydratable: false,
studyDate: formatDate(SeriesDate), // TODO: This is series date. Is that ok?
currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based
currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based
seriesDescription: SeriesDescription,
modality: Modality,
patientInformation: {

View File

@ -1,5 +1,5 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');

View File

@ -1,13 +1,16 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const { merge } = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ROOT_DIR = path.join(__dirname, './..');
const pkg = require('./../package.json');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const ROOT_DIR = path.join(__dirname, './../');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
const fileName = 'index.umd.js';
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
@ -30,7 +33,7 @@ module.exports = (env, argv) => {
},
output: {
path: ROOT_DIR,
library: 'OHIFModeLongitudinal',
library: 'OHIFExtCornerstone',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
@ -39,6 +42,10 @@ module.exports = (env, argv) => {
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new MiniCssExtractPlugin({
filename: './dist/[name].css',
chunkFilename: './dist/[id].css',
}),
],
});
};

View File

@ -1,6 +1,6 @@
{
"name": "@ohif/mode-longitudinal",
"version": "0.0.1",
"version": "3.0.0",
"description": "Longitudinal Workflow",
"author": "OHIF",
"license": "MIT",
@ -8,7 +8,7 @@
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.16.0"
},
@ -19,6 +19,9 @@
"publishConfig": {
"access": "public"
},
"keywords": [
"ohif-mode"
],
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:cornerstone": "yarn run dev",
@ -29,7 +32,13 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "^2.12.3"
"@ohif/core": "^3.0.0",
"@ohif/extension-default": "^3.0.0",
"@ohif/extension-cornerstone": "^3.0.0",
"@ohif/extension-dicom-sr": "^3.0.0",
"@ohif/extension-dicom-pdf": "^3.0.1",
"@ohif/extension-dicom-video": "^3.0.1",
"@ohif/extension-measurement-tracking": "^3.0.0"
},
"dependencies": {
"@babel/runtime": "7.16.3"

View File

@ -0,0 +1,5 @@
import packageJson from '../package.json';
const id = packageJson.name;
export { id };

View File

@ -1,38 +1,53 @@
import toolbarButtons from './toolbarButtons.js';
import { hotkeys } from '@ohif/core';
import toolbarButtons from './toolbarButtons.js';
import { id } from './id.js';
const ohif = {
layout: 'org.ohif.default.layoutTemplateModule.viewerLayout',
sopClassHandler: 'org.ohif.default.sopClassHandlerModule.stack',
hangingProtocols: 'org.ohif.default.hangingProtocolModule.default',
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
hangingProtocols: '@ohif/extension-default.hangingProtocolModule.default',
};
const tracked = {
measurements: 'org.ohif.measurement-tracking.panelModule.trackedMeasurements',
thumbnailList: 'org.ohif.measurement-tracking.panelModule.seriesList',
viewport: 'org.ohif.measurement-tracking.viewportModule.cornerstone-tracked',
measurements:
'@ohif/extension-measurement-tracking.panelModule.trackedMeasurements',
thumbnailList: '@ohif/extension-measurement-tracking.panelModule.seriesList',
viewport:
'@ohif/extension-measurement-tracking.viewportModule.cornerstone-tracked',
};
const dicomsr = {
sopClassHandler: 'org.ohif.dicom-sr.sopClassHandlerModule.dicom-sr',
viewport: 'org.ohif.dicom-sr.viewportModule.dicom-sr',
sopClassHandler: '@ohif/extension-dicom-sr.sopClassHandlerModule.dicom-sr',
viewport: '@ohif/extension-dicom-sr.viewportModule.dicom-sr',
};
const dicomvideo = {
sopClassHandler: 'org.ohif.dicom-video.sopClassHandlerModule.dicom-video',
viewport: 'org.ohif.dicom-video.viewportModule.dicom-video',
}
sopClassHandler:
'@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video',
viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video',
};
const dicompdf = {
sopClassHandler: 'org.ohif.dicom-pdf.sopClassHandlerModule.dicom-pdf',
viewport: 'org.ohif.dicom-pdf.viewportModule.dicom-pdf',
}
sopClassHandler: '@ohif/extension-dicom-pdf.sopClassHandlerModule.dicom-pdf',
viewport: '@ohif/extension-dicom-pdf.viewportModule.dicom-pdf',
};
export default function mode({ modeConfiguration }) {
const extensionDependencies = {
// Can derive the versions at least process.env.from npm_package_version
'@ohif/extension-default': '^3.0.0',
'@ohif/extension-cornerstone': '^3.0.0',
'@ohif/extension-measurement-tracking': '^3.0.0',
'@ohif/extension-dicom-sr': '^3.0.0',
'@ohif/extension-dicom-pdf': '^3.0.1',
'@ohif/extension-dicom-video': '^3.0.1',
};
function modeFactory({ modeConfiguration }) {
return {
// TODO: We're using this as a route segment
// We should not be.
id: 'viewer',
id,
routeName: 'viewer',
displayName: 'Basic Viewer',
/**
* Lifecycle hooks
@ -73,7 +88,7 @@ export default function mode({ modeConfiguration }) {
const modalities_list = modalities.split('\\');
// Slide Microscopy modality not supported by basic mode yet
return !modalities_list.includes('SM')
return !modalities_list.includes('SM');
},
routes: [
{
@ -111,26 +126,26 @@ export default function mode({ modeConfiguration }) {
},
},
],
extensions: [
'org.ohif.default',
'org.ohif.cornerstone',
'org.ohif.measurement-tracking',
'org.ohif.dicom-sr',
'org.ohif.dicom-video',
'org.ohif.dicom-pdf',
],
extensions: extensionDependencies,
hangingProtocols: [ohif.hangingProtocols],
// Order is important in sop class handlers when two handlers both use
// the same sop class under different situations. In that case, the more
// general handler needs to come last. For this case, the dicomvideo msut
// general handler needs to come last. For this case, the dicomvideo must
// come first to remove video transfer syntax before ohif uses images
sopClassHandlers: [
dicomvideo.sopClassHandler,
ohif.sopClassHandler,
dicompdf.sopClassHandler,
dicomsr.sopClassHandler,],
dicomsr.sopClassHandler,
],
hotkeys: [...hotkeys.defaults.hotkeyBindings],
};
}
window.longitudinalMode = mode({});
const mode = {
id,
modeFactory,
extensionDependencies,
};
export default mode;

View File

@ -1,5 +1,5 @@
{
"name": "root",
"name": "ohif-monorepo-root",
"private": true,
"workspaces": {
"packages": [
@ -12,7 +12,7 @@
]
},
"engines": {
"node": ">=10",
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.16.0"
},
@ -22,8 +22,10 @@
"build:dev": "lerna run build:dev --stream",
"build:ci": "lerna run build:viewer:ci --stream",
"build:qa": "lerna run build:viewer:qa --stream",
"cli": "node ./platform/cli/src/index.js",
"build:ui:deploy-preview": "lerna run build:ui:deploy-preview --stream",
"build:demo": "lerna run build:viewer:demo --stream",
"build:package-all": "lerna run build:package --parallel --stream",
"dev": "lerna run dev:viewer --stream",
"dev:project": ".scripts/dev.sh",
"dev:orthanc": "lerna run dev:orthanc --stream",
@ -48,7 +50,7 @@
"lerna:cache": "./netlify-lerna-cache.sh",
"lerna:restore": "./netlify-lerna-restore.sh",
"lerna:version": "npx lerna version prerelease --force-publish",
"lerna:publish": "lerna publish from-package --canary --dist-tag canary",
"lerna:publish": "lerna publish from-package --dist-tag next",
"link-list": "npm ls --depth=0 --link=true"
},
"dependencies": {
@ -62,18 +64,18 @@
"react-dom": "17.0.2"
},
"devDependencies": {
"@babel/core": "^7.5.0",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-arrow-functions": "^7.2.0",
"@babel/plugin-transform-regenerator": "^7.4.5",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/preset-env": "^7.5.0",
"@babel/preset-react": "^7.0.0",
"autoprefixer": "10.2.4",
"@babel/core": "^7.17.8",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-arrow-functions": "^7.16.7",
"@babel/plugin-transform-regenerator": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.17.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"autoprefixer": "10.4.4",
"babel-eslint": "9.x",
"babel-loader": "^8.0.6",
"babel-loader": "^8.2.4",
"babel-plugin-inline-react-svg": "1.1.0",
"babel-plugin-module-resolver": "^3.2.0",
"clean-webpack-plugin": "^3.0.0",
@ -81,17 +83,17 @@
"cross-env": "^5.2.0",
"css-loader": "^3.2.0",
"dotenv": "^8.1.0",
"eslint": "6.8.0",
"eslint-config-prettier": "^6.4.0",
"eslint-config-react-app": "^5.2.0",
"eslint-plugin-flowtype": "2.x",
"eslint-plugin-import": "2.x",
"eslint-plugin-jsx-a11y": "6.x",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "4.2.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^7.2.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-flowtype": "^7.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^5.2.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
"html-webpack-plugin": "^5.3.2",
"husky": "^3.0.0",
"jest": "^24.8.0",
@ -104,7 +106,7 @@
"postcss": "^8.3.5",
"postcss-import": "^14.0.2",
"postcss-loader": "^6.1.1",
"postcss-preset-env": "^6.7.0",
"postcss-preset-env": "^7.4.3",
"prettier": "^1.18.2",
"react-hot-loader": "^4.13.0",
"serve": "^11.1.0",

43
platform/cli/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "@ohif/cli",
"version": "2.0.7",
"description": "A CLI to bootstrap new OHIF extension or mode",
"type": "module",
"main": "src/index.js",
"private": true,
"bin": {
"ohif-cli": "src/index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cli",
"ohif"
],
"author": "OHIF Contributors",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.13.10",
"chalk": "^5.0.0",
"commander": "^8.3.0",
"axios": "^0.26.1",
"execa": "^6.0.0",
"gitignore": "^0.7.0",
"inquirer": "^8.2.0",
"listr": "^0.14.3",
"mustache": "^4.2.0",
"ncp": "^2.0.0",
"node-fetch": "^3.1.1",
"pkg-install": "^1.0.0",
"registry-url": "^6.0.0",
"spdx-license-list": "^6.4.0",
"util": "^0.12.4",
"yarn-programmatic": "^0.1.2"
},
"files": [
"bin/",
"src/",
"templates/"
]
}

View File

@ -0,0 +1,55 @@
import Listr from 'listr';
import chalk from 'chalk';
import {
installNPMPackage,
getYarnInfo,
validateExtension,
getVersionedPackageName,
addExtensionToConfig,
} from './utils/index.js';
export default async function addExtension(packageName, version) {
console.log(chalk.green.bold(`Adding ohif-extension ${packageName}...`));
const versionedPackageName = getVersionedPackageName(packageName, version);
const tasks = new Listr(
[
{
title: `Searching for extension: ${versionedPackageName}`,
task: async () => await validateExtension(packageName, version),
},
{
title: `Installing npm package: ${versionedPackageName}`,
task: async () => await installNPMPackage(packageName, version),
},
{
title: 'Adding ohif-extension to the configuration file',
task: async ctx => {
const yarnInfo = await getYarnInfo(packageName);
addExtensionToConfig(packageName, yarnInfo);
ctx.yarnInfo = yarnInfo;
},
},
],
{
exitOnError: true,
}
);
await tasks
.run()
.then(ctx => {
console.log(
`${chalk.green.bold(
`Added ohif-extension ${packageName}@${ctx.yarnInfo.version}`
)} `
);
})
.catch(error => {
console.log(error.message);
});
}

View File

@ -0,0 +1,38 @@
import Listr from 'listr';
import chalk from 'chalk';
import addExtension from './addExtension.js';
export default async function addExtensions(ohifExtensions) {
// Auto generate Listr tasks...
const taskEntries = [];
ohifExtensions.forEach(({ packageName, version }) => {
const title = `Adding ohif-extension ${packageName}`;
taskEntries.push({
title,
task: async () => await addExtension(packageName, version),
});
});
const tasks = new Listr(taskEntries, {
exitOnError: true,
});
await tasks
.run()
.then(() => {
let extensonsString = '';
ohifExtensions.forEach(({ packageName, version }) => {
extensonsString += ` ${packageName}@${version}`;
});
console.log(
`${chalk.green.bold(`Extensions added:${extensonsString}`)} `
);
})
.catch(error => {
console.log(error.message);
});
}

View File

@ -0,0 +1,72 @@
import Listr from 'listr';
import chalk from 'chalk';
import {
installNPMPackage,
getYarnInfo,
getVersionedPackageName,
validateMode,
addModeToConfig,
findRequiredOhifExtensionsForMode,
} from './utils/index.js';
import addExtensions from './addExtensions.js';
export default async function addMode(packageName, version) {
console.log(chalk.green.bold(`Adding ohif-mode ${packageName}...`));
const versionedPackageName = getVersionedPackageName(packageName, version);
const tasks = new Listr(
[
{
title: `Searching for mode: ${versionedPackageName}`,
task: async () => await validateMode(packageName, version),
},
{
title: `Installing npm package: ${versionedPackageName}`,
task: async () => await installNPMPackage(packageName, version),
},
{
title: 'Adding ohif-mode to the configuration file',
task: async ctx => {
const yarnInfo = await getYarnInfo(packageName);
addModeToConfig(packageName, yarnInfo);
ctx.yarnInfo = yarnInfo;
},
},
{
title: 'Detecting required ohif-extensions...',
task: async ctx => {
ctx.ohifExtensions = await findRequiredOhifExtensionsForMode(
ctx.yarnInfo
);
},
},
],
{
exitOnError: true,
}
);
await tasks
.run()
.then(async ctx => {
console.log(
`${chalk.green.bold(
`Added ohif-mode ${packageName}@${ctx.yarnInfo.version}`
)} `
);
const ohifExtensions = ctx.ohifExtensions;
if (ohifExtensions.length) {
console.log(`${chalk.green.bold(`Installing dependent extensions`)} `);
await addExtensions(ohifExtensions);
}
})
.catch(error => {
console.log(error.message);
});
}

View File

@ -0,0 +1 @@
export default 'Not found';

View File

@ -0,0 +1,97 @@
import Listr from 'listr';
import chalk from 'chalk';
import fs from 'fs';
import {
createDirectoryContents,
editPackageJson,
createLicense,
createReadme,
initGit,
} from './utils/index.js';
const createPackage = async (options) => {
const { packageType } = options; // extension or mode
if (fs.existsSync(options.targetDir)) {
console.error(
`%s ${packageType} with the same name already exists in this directory, either delete it or choose a different name`,
chalk.red.bold('ERROR')
);
process.exit(1);
}
fs.mkdirSync(options.targetDir);
const tasks = new Listr(
[
{
title: 'Copying template files',
task: () =>
createDirectoryContents(
options.templateDir,
options.targetDir,
options.prettier
),
},
{
title: 'Editing Package.json with provided information',
task: () => editPackageJson(options),
},
{
title: 'Creating a License file',
task: () => createLicense(options),
},
{
title: 'Creating a Readme file',
task: () => createReadme(options),
},
{
title: 'Initializing a Git Repository',
enabled: () => options.gitRepository,
task: () => initGit(options),
},
],
{
exitOnError: true,
}
);
await tasks.run();
console.log();
console.log(
chalk.green(`Done: ${packageType} is ready at`, options.targetDir)
);
console.log();
console.log(
chalk.green(`NOTE: In order to use this ${packageType} for development,`)
);
console.log(
chalk.green(
`run the following command inside the root of the OHIF monorepo`
)
);
console.log();
console.log(
chalk.green.bold(
` yarn run cli link-${packageType} ${options.targetDir}`
)
);
console.log();
console.log(
chalk.yellow(
"and when you don't need it anymore, run the following command to unlink it"
)
);
console.log();
console.log(
chalk.yellow(` yarn run cli unlink-${packageType} ${options.name}`)
);
console.log();
return true;
};
export default createPackage;

View File

@ -0,0 +1,8 @@
const colors = {
LIGHT: '#5acce6',
MAIN: '#0944b3',
DARK: '#090c29',
ACTIVE: '#348cfd',
};
export default colors;

View File

@ -0,0 +1,5 @@
const endPoints = {
NPM_KEYWORD: 'https://registry.npmjs.com/-/v1/search?text=keywords:',
};
export default endPoints;

View File

@ -0,0 +1,5 @@
import keywords from './keywords.js';
import colors from './colors.js';
import endPoints from './endPoints.js';
export { keywords, colors, endPoints };

View File

@ -0,0 +1,6 @@
const keywords = {
MODE: 'ohif-mode',
EXTENSION: 'ohif-extension',
};
export default keywords;

View File

@ -0,0 +1,23 @@
import createPackage from './createPackage.js';
import addExtension from './addExtension.js';
import removeExtension from './removeExtension.js';
import addMode from './addMode.js';
import removeMode from './removeMode.js';
import listPlugins from './listPlugins.js';
import searchPlugins from './searchPlugins.js';
import { linkExtension, linkMode } from './linkPackage.js';
import { unlinkExtension, unlinkMode } from './unlinkPackage.js';
export {
createPackage,
addExtension,
removeExtension,
addMode,
removeMode,
listPlugins,
searchPlugins,
linkExtension,
linkMode,
unlinkExtension,
unlinkMode,
};

View File

@ -0,0 +1,56 @@
import fs from 'fs';
import path from 'path';
import { execa } from 'execa';
import { keywords } from './enums/index.js';
import {
validateYarn,
addExtensionToConfig,
addModeToConfig,
} from './utils/index.js';
async function linkPackage(packageDir, options, addToConfig, keyword) {
const { viewerDirectory } = options;
// read package.json from packageDir
const file = fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8');
// name of the package
const packageJSON = JSON.parse(file);
const packageName = packageJSON.name;
const packageKeywords = packageJSON.keywords;
// check if package is an extension or a mode
if (!packageKeywords.includes(keyword)) {
throw new Error(`${packageName} is not ${keyword}`);
}
const version = packageJSON.version;
// make sure yarn is installed
await validateYarn();
// change directory to packageDir and execute yarn link
process.chdir(packageDir);
let results;
results = await execa(`yarn`, ['link']);
// change directory to OHIF Platform root and execute yarn link
process.chdir(viewerDirectory);
results = await execa(`yarn`, ['link', packageName]);
console.log(results.stdout);
addToConfig(packageName, { version });
}
function linkExtension(packageDir, options) {
const keyword = keywords.EXTENSION;
linkPackage(packageDir, options, addExtensionToConfig, keyword);
}
function linkMode(packageDir, options) {
const keyword = keywords.MODE;
linkPackage(packageDir, options, addModeToConfig, keyword);
}
export { linkExtension, linkMode };

View File

@ -0,0 +1,23 @@
import fs from 'fs';
import { prettyPrint } from './utils/index.js';
import { colors } from './enums/index.js';
const listPlugins = async configPath => {
const pluginConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const { extensions, modes } = pluginConfig;
const titleOptions = { color: colors.LIGHT, bold: true };
const itemsOptions = { color: colors.ACTIVE, bold: true };
const extensionsItems = extensions.map(
extension => `${extension.packageName} @ ${extension.version}`
);
const modesItems = modes.map(mode => `${mode.packageName} @ ${mode.version}`);
prettyPrint('Extensions', titleOptions, extensionsItems, itemsOptions);
prettyPrint('Modes', titleOptions, modesItems, itemsOptions);
};
export default listPlugins;

View File

@ -0,0 +1,49 @@
import chalk from 'chalk';
import Listr from 'listr';
import {
uninstallNPMPackage,
throwIfExtensionUsedByInstalledMode,
removeExtensionFromConfig,
validateExtensionYarnInfo,
} from './utils/index.js';
export default async function removeExtension(packageName) {
console.log(chalk.green.bold(`Removing ohif-extension ${packageName}...`));
const tasks = new Listr(
[
{
title: `Searching for installed extension: ${packageName}`,
task: async () => await validateExtensionYarnInfo(packageName),
},
{
title: `Checking if ${packageName} is in use by an installed mode`,
task: async () =>
await throwIfExtensionUsedByInstalledMode(packageName),
},
{
title: `Uninstalling npm package: ${packageName}`,
task: async () => await uninstallNPMPackage(packageName),
},
{
title: 'Removing ohif-extension from the configuration file',
task: async () => removeExtensionFromConfig(packageName),
},
],
{
exitOnError: true,
}
);
await tasks
.run()
.then(() => {
console.log(
`${chalk.green.bold(`Removed ohif-extension ${packageName}`)} `
);
})
.catch(error => {
console.log(error.message);
});
}

View File

@ -0,0 +1,38 @@
import Listr from 'listr';
import chalk from 'chalk';
import removeExtension from './removeExtension.js';
export default async function removeExtensions(ohifExtensionsToRemove) {
// Auto generate Listr tasks...
const taskEntries = [];
ohifExtensionsToRemove.forEach(packageName => {
const title = `Removing ohif-extension ${packageName}`;
taskEntries.push({
title,
task: async () => await removeExtension(packageName),
});
});
const tasks = new Listr(taskEntries, {
exitOnError: true,
});
await tasks
.run()
.then(() => {
let extensonsString = '';
ohifExtensionsToRemove.forEach(packageName => {
extensonsString += ` ${packageName}`;
});
console.log(
`${chalk.green.bold(`Extensions removed:${extensonsString}`)} `
);
})
.catch(error => {
console.log(error.message);
});
}

View File

@ -0,0 +1,68 @@
import Listr from 'listr';
import chalk from 'chalk';
import {
uninstallNPMPackage,
findOhifExtensionsToRemoveAfterRemovingMode,
removeModeFromConfig,
validateModeYarnInfo,
getYarnInfo,
} from './utils/index.js';
import removeExtensions from './removeExtensions.js';
export default async function removeMode(packageName) {
console.log(chalk.green.bold(`Removing ohif-mode ${packageName}...`));
const tasks = new Listr(
[
{
title: `Searching for installed mode: ${packageName}`,
task: async ctx => {
ctx.yarnInfo = await getYarnInfo(packageName);
await validateModeYarnInfo(packageName);
},
},
{
title: `Uninstalling npm package: ${packageName}`,
task: async () => await uninstallNPMPackage(packageName),
},
{
title: 'Removing ohif-mode from the configuration file',
task: async () => await removeModeFromConfig(packageName),
},
{
title: 'Detecting extensions that can be removed...',
task: async ctx => {
ctx.ohifExtensionsToRemove = await findOhifExtensionsToRemoveAfterRemovingMode(
ctx.yarnInfo
);
},
},
],
{
exitOnError: true,
}
);
await tasks
.run()
.then(async ctx => {
// Remove extensions if they aren't used by any other mode.
console.log(`${chalk.green.bold(`Removed ohif-mode ${packageName}`)} `);
const ohifExtensionsToRemove = ctx.ohifExtensionsToRemove;
if (ohifExtensionsToRemove.length) {
console.log(
`${chalk.green.bold(
`Removing ${ohifExtensionsToRemove.length} extensions no longer used by any installed mode`
)}`
);
await removeExtensions(ohifExtensionsToRemove);
}
})
.catch(error => {
console.log(error.message);
});
}

View File

@ -0,0 +1,57 @@
import axios from 'axios';
import { prettyPrint } from './utils/index.js';
import { keywords, colors, endPoints } from './enums/index.js';
async function searchRegistry(keyword) {
const url = `${endPoints.NPM_KEYWORD}${keyword}`;
try {
const response = await axios.get(url);
const { objects } = response.data;
return objects;
} catch (error) {
console.log(error);
}
}
async function searchPlugins(options) {
const { verbose } = options;
const extensions = await searchRegistry(keywords.EXTENSION);
const modes = await searchRegistry(keywords.MODE);
const titleOptions = { color: colors.LIGHT, bold: true };
const itemsOptions = {};
const extensionsItems = extensions.map(extension => {
const item = [
`${extension.package.name} @ ${extension.package.version}`,
[`Description: ${extension.package.description}`],
];
if (verbose) {
item[1].push(`Repository: ${extension.package.links.repository}`);
}
return item;
});
const modesItems = modes.map(mode => {
const item = [
`${mode.package.name} @ ${mode.package.version}`,
[`Description: ${mode.package.description}`],
];
if (verbose) {
item[1].push(`Repository: ${mode.package.links.repository}`);
}
return item;
});
prettyPrint('Extensions', titleOptions, extensionsItems, itemsOptions);
prettyPrint('Modes', titleOptions, modesItems, itemsOptions);
}
export default searchPlugins;

View File

@ -0,0 +1,32 @@
import { execa } from 'execa';
import {
validateYarn,
removeExtensionFromConfig,
removeModeFromConfig,
} from './utils/index.js';
const linkPackage = async (packageName, options, removeFromConfig) => {
const { viewerDirectory } = options;
// make sure yarn is installed
await validateYarn();
// change directory to OHIF Platform root and execute yarn link
process.chdir(viewerDirectory);
const results = await execa(`yarn`, ['unlink', packageName]);
console.log(results.stdout);
//update the plugin.json file
removeFromConfig(packageName);
};
function unlinkExtension(extensionName, options) {
linkPackage(extensionName, options, removeExtensionFromConfig);
}
function unlinkMode(modeName, options) {
linkPackage(modeName, options, removeModeFromConfig);
}
export { unlinkExtension, unlinkMode };

View File

@ -0,0 +1,34 @@
import {
addExtensionToConfigJson,
addModeToConfigJson,
readPluginConfigFile,
writePluginConfigFile,
} from './private/index.js';
function addToAndOverwriteConfig(packageName, options, augmentConfigFunction) {
const installedVersion = options.version;
let pluginConfig = readPluginConfigFile();
if (!pluginConfig) {
pluginConfig = {
extensions: [],
modes: [],
};
}
augmentConfigFunction(pluginConfig, {
packageName,
version: installedVersion,
});
writePluginConfigFile(pluginConfig);
}
function addExtensionToConfig(packageName, options) {
addToAndOverwriteConfig(packageName, options, addExtensionToConfigJson);
}
function addModeToConfig(packageName, options) {
addToAndOverwriteConfig(packageName, options, addModeToConfigJson);
}
export { addExtensionToConfig, addModeToConfig };

View File

@ -0,0 +1,42 @@
import fs from 'fs';
// https://github.dev/leoroese/template-cli/blob/628dd24db7df399ebb520edd0bc301bc7b5e8b66/index.js#L19
const createDirectoryContents = (
templatePath,
targetDirPath,
copyPrettierRules
) => {
const filesToCreate = fs.readdirSync(templatePath);
filesToCreate.forEach(file => {
if (!copyPrettierRules && file === '.prettierrc') {
return;
}
const origFilePath = `${templatePath}/${file}`;
// get stats about the current file
const stats = fs.statSync(origFilePath);
if (stats.isFile()) {
const contents = fs.readFileSync(origFilePath, 'utf8');
// Rename
if (file === '.npmignore') file = '.gitignore';
const writePath = `${targetDirPath}/${file}`;
fs.writeFileSync(writePath, contents, 'utf8');
} else if (stats.isDirectory()) {
fs.mkdirSync(`${targetDirPath}/${file}`);
// recursive call
createDirectoryContents(
`${templatePath}/${file}`,
`${targetDirPath}/${file}`,
copyPrettierRules
);
}
});
};
export default createDirectoryContents;

View File

@ -0,0 +1,31 @@
import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import spdxLicenseList from 'spdx-license-list/full.js';
const writeFile = promisify(fs.writeFile);
async function createLicense(options) {
const { targetDir, name, email } = options;
const targetPath = path.join(targetDir, 'LICENSE');
let license;
try {
license = spdxLicenseList[options.license];
} catch (err) {
console.error(
'%s License %s not found in the list of licenses',
chalk.red.bold('ERROR'),
options.license
);
process.exit(1);
}
const licenseContent = license.licenseText
.replace('<year>', new Date().getFullYear())
.replace('<copyright holders>', `${name} (${email})`);
return writeFile(targetPath, licenseContent, 'utf8');
}
export default createLicense;

View File

@ -0,0 +1,23 @@
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import mustache from 'mustache';
const writeFile = promisify(fs.writeFile);
async function createReadme(options) {
let template = `# {{name}} \n## Description \n{{description}} \n## Author \n{{author}} \n## License \n{{license}}`;
const { name, description, author, license, targetDir } = options;
const targetPath = path.join(targetDir, 'README.md');
const readmeContent = mustache.render(template, {
name,
description,
author,
license,
});
return writeFile(targetPath, readmeContent, 'utf8');
}
export default createReadme;

View File

@ -0,0 +1,33 @@
import fs from 'fs';
import path from 'path';
async function editPackageJson(options) {
const { name, version, description, author, license, targetDir } = options;
// read package.json from targetDir
const dependenciesPath = path.join(targetDir, 'dependencies.json');
const rawData = fs.readFileSync(dependenciesPath, 'utf8');
const packageJson = JSON.parse(rawData);
// edit package.json
const mergedObj = Object.assign(
{
name,
version,
description,
author,
license,
files: ['dist', 'README.md'],
},
packageJson
);
// write package.json back to targetDir
const writePath = path.join(targetDir, 'package.json');
fs.writeFileSync(writePath, JSON.stringify(mergedObj, null, 2));
// remove the dependencies.json file
fs.unlinkSync(dependenciesPath);
}
export default editPackageJson;

View File

@ -0,0 +1,67 @@
import { readPluginConfigFile } from './private/index.js';
import getYarnInfo from './getYarnInfo.js';
export default async function findOhifExtensionsToRemoveAfterRemovingMode(
removedModeYarnInfo
) {
const pluginConfig = readPluginConfigFile();
if (!pluginConfig) {
// No other modes or extensions, no action item.
return [];
}
const { modes, extensions } = pluginConfig;
const registeredExtensions = extensions.map(
extension => extension.packageName
);
// TODO this is not a function
const ohifExtensionsOfMode = Object.keys(
removedModeYarnInfo.peerDependencies
).filter(peerDependency => registeredExtensions.includes(peerDependency));
const ohifExtensionsUsedInOtherModes = ohifExtensionsOfMode.map(
packageName => {
return {
packageName,
used: false,
};
}
);
// Check if other modes use each extension used by this mode
const otherModes = modes.filter(
mode => mode.packageName !== removedModeYarnInfo.name
);
for (let i = 0; i < otherModes.length; i++) {
const mode = otherModes[i];
const yarnInfo = await getYarnInfo(mode.packageName);
const peerDependencies = yarnInfo.peerDependencies;
if (!peerDependencies) {
continue;
}
for (let j = 0; j < ohifExtensionsUsedInOtherModes.length; j++) {
const ohifExtension = ohifExtensionsUsedInOtherModes[j];
if (ohifExtension.used) {
// Already accounted that we can't delete this, so don't waste effort
return;
}
if (Object.keys(peerDependencies).includes(ohifExtension.packageName)) {
ohifExtension.used = true;
}
}
}
// Return list of now unused extensions
const ohifExtensionsToRemove = ohifExtensionsUsedInOtherModes
.filter(ohifExtension => !ohifExtension.used)
.map(ohifExtension => ohifExtension.packageName);
return ohifExtensionsToRemove;
}

View File

@ -0,0 +1,41 @@
import { validateExtension } from './validate.js';
export default async function findRequiredOhifExtensionsForMode(yarnInfo) {
// Get yarn info file and get peer dependencies
if (!yarnInfo.peerDependencies) {
// No ohif-extension dependencies
return;
}
const peerDependencies = yarnInfo.peerDependencies;
const dependencies = [];
const ohifExtensions = [];
Object.keys(peerDependencies).forEach((packageName) => {
dependencies.push({
packageName,
version: peerDependencies[packageName],
});
});
const promises = [];
// Fetch each npm json and check which are ohif extensions
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
const { packageName, version } = dependency;
const promise = validateExtension(packageName, version)
.then(() => {
ohifExtensions.push({ packageName, version });
})
.catch(() => {});
promises.push(promise);
}
// Await all the extensions // TODO -> Improve so we async install each
// extension and await all of those promises instead.
await Promise.all(promises);
return ohifExtensions;
}

View File

@ -0,0 +1,3 @@
export default function getVersionedPackageName(packageName, version) {
return version === undefined ? packageName : `${packageName}@${version}`;
}

View File

@ -0,0 +1,5 @@
import { info } from 'yarn-programmatic';
export default async function getYarnInfo(packageName) {
return await info(packageName);
}

View File

@ -0,0 +1,50 @@
import getVersionedPackageName from './getVersionedPackageName.js';
import installNPMPackage from './installNPMPackage.js';
import uninstallNPMPackage from './uninstallNPMPackage.js';
import {
validateMode,
validateExtension,
validateModeYarnInfo,
validateExtensionYarnInfo,
} from './validate.js';
import getYarnInfo from './getYarnInfo.js';
import { addExtensionToConfig, addModeToConfig } from './addToConfig.js';
import findRequiredOhifExtensionsForMode from './findRequiredOhifExtensionsForMode.js';
import {
removeExtensionFromConfig,
removeModeFromConfig,
} from './removeFromConfig.js';
import throwIfExtensionUsedByInstalledMode from './throwIfExtensionUsedByInstalledMode.js';
import findOhifExtensionsToRemoveAfterRemovingMode from './findOhifExtensionsToRemoveAfterRemovingMode.js';
import initGit from './initGit.js';
import createDirectoryContents from './createDirectoryContents.js';
import editPackageJson from './editPackageJson.js';
import createLicense from './createLicense.js';
import createReadme from './createReadme.js';
import prettyPrint from './prettyPrint.js';
import validateYarn from './validateYarn.js';
export {
getYarnInfo,
getVersionedPackageName,
installNPMPackage,
uninstallNPMPackage,
validateMode,
validateExtension,
validateModeYarnInfo,
validateExtensionYarnInfo,
addExtensionToConfig,
addModeToConfig,
findRequiredOhifExtensionsForMode,
removeExtensionFromConfig,
throwIfExtensionUsedByInstalledMode,
removeModeFromConfig,
findOhifExtensionsToRemoveAfterRemovingMode,
initGit,
createDirectoryContents,
editPackageJson,
createLicense,
createReadme,
prettyPrint,
validateYarn,
};

View File

@ -0,0 +1,35 @@
import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { execa } from 'execa';
const exists = promisify(fs.exists);
async function initGit(options) {
const { targetDir } = options;
const targetPath = path.join(targetDir, '.git');
// Check if git is installed
try {
await execa('git', ['--version']);
} catch (err) {
console.error(
'%s Git is not installed. Please install git and try again.',
chalk.red.bold('ERROR')
);
process.exit(1);
}
if (!(await exists(targetPath))) {
try {
await execa('git', ['init'], { cwd: targetDir });
} catch (err) {
console.error('%s Failed to initialize git', chalk.red.bold('ERROR'));
console.error(err);
process.exit(1);
}
}
}
export default initGit;

View File

@ -0,0 +1,14 @@
import { install } from 'pkg-install';
const installNPMPackage = async (packageName, version) => {
let installObject = {};
installObject[packageName] = version;
await install(installObject, {
prefer: 'yarn',
cwd: process.cwd(),
});
};
export default installNPMPackage;

View File

@ -0,0 +1,79 @@
import chalk from 'chalk';
import { colors } from '../enums/index.js';
function getStyle({ color, bold }) {
return bold ? chalk.hex(color).bold : chalk.hex(color);
}
function levelOnePrint(items) {
let output = '';
if (Array.isArray(items)) {
items.forEach(item => {
output += ` |- ${item}\n`;
});
return output;
}
return ` |- ${items}\n`;
}
function levelTwoPrint(items) {
let output = '';
items.forEach(item => {
output += ` | |- ${item}\n`;
});
return output;
}
/**
*
* @param {string} title Title of the section
* @param {object} titleOptions Options for the title includes color and bold
* @param { [] | [][] } items Array of items to display, OR a list of lists
* @param {object} itemOptions Options for the items includes color and bold
*
*
* items= ['Mode-A', 'Mode-B', 'Mode-C']
*
* |- Mode-A
* |- Mode-B
* |- Mode-C
*
* items = [['Mode-A', ['Description-A', 'Authors-A', 'Repository-A]], ['Mode-B', ['Description-B', 'Authors-B', 'Repository-B]], ['Mode-C', ['Description-C', 'Authors-C', 'Repository-C]]]
*
* |- Mode-A
* | |- Description-A
* | |- Authors-A
* | |- Repository-A
* |
* |- Mode-B
* | |- Description-B
* | |- Authors-B
* | |- Repository-B
*
*
*/
function prettyPrint(
title,
titleOptions = { color: colors.MAIN, bold: true },
itemsArray = [[]],
itemOptions = {}
) {
console.log('');
console.log(getStyle(titleOptions)(title));
let output = '';
itemsArray.forEach(items => {
if (!Array.isArray(items)) {
output += levelOnePrint(items);
} else {
output += levelOnePrint(items[0]);
output += levelTwoPrint(items[1]);
}
});
const itmeStyle = itemOptions.color ? getStyle(itemOptions)(output) : output;
console.log(itmeStyle);
}
export default prettyPrint;

View File

@ -0,0 +1,15 @@
export default function getPackageNameAndScope(packageName) {
let scope;
let packageNameLessScope;
if (packageName.includes('@')) {
[scope, packageNameLessScope] = packageName.split('/');
} else {
packageNameLessScope = packageName;
}
return {
scope,
packageNameLessScope,
};
}

View File

@ -0,0 +1,19 @@
import getPackageNameAndScope from './getPackageNameAndScope.js';
import {
addExtensionToConfigJson,
removeExtensionFromConfigJson,
addModeToConfigJson,
removeModeFromConfigJson,
} from './manipulatePluginConfigFile.js';
import writePluginConfigFile from './writePluginConfigFile.js';
import readPluginConfigFile from './readPluginConfigFile.js';
export {
getPackageNameAndScope,
addExtensionToConfigJson,
removeExtensionFromConfigJson,
addModeToConfigJson,
removeModeFromConfigJson,
readPluginConfigFile,
writePluginConfigFile,
};

View File

@ -0,0 +1,40 @@
function addExtensionToConfigJson(pluginConfig, { packageName, version }) {
addToList('extensions', pluginConfig, { packageName, version });
}
function addModeToConfigJson(pluginConfig, { packageName, version }) {
addToList('modes', pluginConfig, { packageName, version });
}
function removeExtensionFromConfigJson(pluginConfig, { packageName }) {
removeFromList('extensions', pluginConfig, { packageName });
}
function removeModeFromConfigJson(pluginConfig, { packageName }) {
removeFromList('modes', pluginConfig, { packageName });
}
function removeFromList(listName, pluginConfig, { packageName }) {
const list = pluginConfig[listName];
const indexOfExistingEntry = list.findIndex(
entry => entry.packageName === packageName
);
if (indexOfExistingEntry !== -1) {
pluginConfig[listName].splice(indexOfExistingEntry, 1);
}
}
function addToList(listName, pluginConfig, { packageName, version }) {
removeFromList(listName, pluginConfig, { packageName });
pluginConfig[listName].push({ packageName, version });
}
export {
addExtensionToConfigJson,
addModeToConfigJson,
removeExtensionFromConfigJson,
removeModeFromConfigJson,
};

View File

@ -0,0 +1,17 @@
import fs from 'fs';
export default function readPluginConfigFile() {
let fileContents;
try {
fileContents = fs.readFileSync('./pluginConfig.json', { flag: 'r' });
} catch (err) {
return; // File doesn't exist yet.
}
if (fileContents) {
fileContents = JSON.parse(fileContents);
}
return fileContents;
}

View File

@ -0,0 +1,18 @@
import fs from 'fs';
export default function writePluginConfigFile(pluginConfig) {
// Note: Second 2 arguments are to pretty print the JSON so its human readable.
const jsonStringOfFileContents = JSON.stringify(pluginConfig, null, 2);
fs.writeFileSync(
`./pluginConfig.json`,
jsonStringOfFileContents,
{ flag: 'w+' },
(err) => {
if (err) {
console.error(err);
return;
}
}
);
}

View File

@ -0,0 +1,26 @@
import {
removeExtensionFromConfigJson,
removeModeFromConfigJson,
writePluginConfigFile,
readPluginConfigFile,
} from './private/index.js';
function removeFromAndOverwriteConfig(packageName, augmentConfigFunction) {
const pluginConfig = readPluginConfigFile();
// Note: if file is not found, nothing to remove.
if (pluginConfig) {
augmentConfigFunction(pluginConfig, { packageName });
writePluginConfigFile(pluginConfig);
}
}
function removeExtensionFromConfig(packageName) {
removeFromAndOverwriteConfig(packageName, removeExtensionFromConfigJson);
}
function removeModeFromConfig(packageName) {
removeFromAndOverwriteConfig(packageName, removeModeFromConfigJson);
}
export { removeExtensionFromConfig, removeModeFromConfig };

View File

@ -0,0 +1,48 @@
import { readPluginConfigFile } from './private/index.js';
import getYarnInfo from './getYarnInfo.js';
import chalk from 'chalk';
export default async function throwIfExtensionUsedByInstalledMode(packageName) {
const pluginConfig = readPluginConfigFile();
if (!pluginConfig) {
// No other modes, not in use
return false;
}
const { modes } = pluginConfig;
const modesUsingExtension = [];
for (let i = 0; i < modes.length; i++) {
const mode = modes[i];
const modePackageName = mode.packageName;
const yarnInfo = await getYarnInfo(modePackageName);
const peerDependencies = yarnInfo.peerDependencies;
if (!peerDependencies) {
continue;
}
if (Object.keys(peerDependencies).includes(packageName)) {
modesUsingExtension.push(modePackageName);
}
}
if (modesUsingExtension.length > 0) {
let modesString = '';
modesUsingExtension.forEach(packageName => {
modesString += ` ${packageName}`;
});
const error = new Error(
`${chalk.yellow.red(
'Error'
)} ohif-extension ${packageName} used by installed modes:${modesString}`
);
throw error;
}
}

View File

@ -0,0 +1,12 @@
import { remove } from 'yarn-programmatic';
const uninstallNPMPackage = async packageName => {
// TODO - Anoyingly pkg-install doesn't seem to have uninstall.
// So since we are using yarn we will just use yarn here, but the tool
// is certainly less generic. But its a super minor issue.
await remove(packageName).catch(err => {
console.log(err);
});
};
export default uninstallNPMPackage;

View File

@ -0,0 +1,160 @@
import registryUrl from 'registry-url';
import keywords from '../enums/keywords.js';
import { getPackageNameAndScope } from './private/index.js';
import chalk from 'chalk';
import fetch from 'node-fetch';
import getYarnInfo from './getYarnInfo.js';
import NOT_FOUND from '../constants/notFound.js';
async function validateMode(packageName, version) {
return validate(packageName, version, keywords.MODE);
}
async function validateExtension(packageName, version) {
return validate(packageName, version, keywords.EXTENSION);
}
async function validateModeYarnInfo(packageName) {
return validateYarnInfo(packageName, keywords.MODE);
}
async function validateExtensionYarnInfo(packageName) {
return validateYarnInfo(packageName, keywords.EXTENSION);
}
function validateYarnInfo(packageName, keyword) {
return new Promise(async (resolve, reject) => {
function rejectIfNotFound() {
const error = new Error(
`${chalk.red.bold('Error')} extension ${packageName} not installed`
);
reject(error);
}
const packageInfo = await getYarnInfo(packageName).catch(() => {
rejectIfNotFound();
});
if (!packageInfo) {
rejectIfNotFound();
return;
}
const { keywords } = packageInfo;
const isValid = keywords && keywords.includes(keyword);
if (isValid) {
resolve(true);
} else {
const error = new Error(
`${chalk.red.bold('Error')} package ${packageName} is not an ${keyword}`
);
reject(error);
}
});
}
function getVersion(json, version) {
const versions = Object.keys(json.versions);
// if no version is defined get the latest
if (version === undefined) {
return json['dist-tags'].latest;
}
// Get and validate version if it is explicitly defined
const allowMinorVersionUpgrade = version.startsWith('^');
if (!allowMinorVersionUpgrade) {
const isValidVersion = versions.includes(version);
if (!isValidVersion) {
return;
}
return version;
}
// Choose version based on the newer minor/patch versions
const [majorVersion] = version
.split('^')[1]
.split('.')
.map((v) => parseInt(v));
// Find the version that matches the major version, but is the latest minor version
versions
.filter((version) => parseInt(version.split('.')[0]) === majorVersion)
.sort((a, b) => {
const [majorA, minorA, patchA] = a.split('.').map((v) => parseInt(v));
const [majorB, minorB, patchB] = b.split('.').map((v) => parseInt(v));
if (majorA === majorB) {
if (minorA === minorB) {
return patchB - patchA;
}
return minorB - minorA;
}
return majorB - majorA;
});
if (versions.length === 0) {
return;
}
return versions[0];
}
function validate(packageName, version, keyword) {
return new Promise(async (resolve, reject) => {
const { scope } = getPackageNameAndScope(packageName);
// Gets the registry of the package. Scoped packages may not be using the global default.
const registryUrlOfPackage = registryUrl(scope);
const response = await fetch(`${registryUrlOfPackage}${packageName}`);
const json = await response.json();
if (json.error && json.error === NOT_FOUND) {
const error = new Error(
`${chalk.red.bold('Error')} package ${packageName} not found`
);
reject(error);
return;
}
const packageVersion = getVersion(json, version);
if (packageVersion) {
const versionedJson = json.versions[packageVersion];
const keywords = versionedJson.keywords;
const isValid = keywords && keywords.includes(keyword);
if (isValid) {
resolve(true);
} else {
const error = new Error(
`${chalk.red.bold(
'Error'
)} package ${packageName} is not an ${keyword}`
);
reject(error);
}
} else {
// Particular version undefined
const error = new Error(
`${chalk.red.bold(
'Error'
)} version ${packageVersion} of package ${packageName} not found`
);
reject(error);
}
});
}
export {
validateMode,
validateExtension,
validateModeYarnInfo,
validateExtensionYarnInfo,
};

View File

@ -0,0 +1,14 @@
import chalk from 'chalk';
import { execa } from 'execa';
export default async function validateYarn() {
try {
await execa('yarn', ['--version']);
} catch (err) {
console.log(
'%s Yarn is not installed, please install it before linking your extension',
chalk.red.bold('ERROR')
);
process.exit(1);
}
}

210
platform/cli/src/index.js Executable file
View File

@ -0,0 +1,210 @@
#!/usr/bin/env node
import program from 'commander';
import inquirer from 'inquirer';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { getPathQuestions, getRepoQuestions } from './questions.js';
import {
createPackage,
addExtension,
removeExtension,
addMode,
removeMode,
listPlugins,
searchPlugins,
linkExtension,
linkMode,
unlinkExtension,
unlinkMode,
} from './commands/index.js';
import chalk from 'chalk';
const runningDirectory = process.cwd();
const viewerDirectory = path.resolve(runningDirectory, 'platform/viewer');
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageJsonPath = path.join(runningDirectory, 'package.json');
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (packageJson.name !== 'ohif-monorepo-root') {
console.log(packageJson);
console.log(
chalk.red('ohif-cli must run from the root of the OHIF platform')
);
process.exit(1);
}
} catch (error) {
console.log(
chalk.red('ohif-cli must run from the root of the OHIF platform')
);
process.exit(1);
}
function _createPackage(packageType) {
const pathQuestions = getPathQuestions(packageType);
const repoQuestions = getRepoQuestions(packageType);
let pathAnswers;
const askPathQuestions = () => {
inquirer.prompt(pathQuestions).then((answers) => {
pathAnswers = answers;
if (pathAnswers.confirm) {
askRepoQuestions(answers.baseDir, answers.name);
} else {
askPathQuestions();
}
});
};
const askRepoQuestions = () => {
inquirer.prompt(repoQuestions).then((repoAnswers) => {
const answers = {
...pathAnswers,
...repoAnswers,
};
const templateDir = path.join(__dirname, `../templates/${packageType}`);
answers.templateDir = templateDir;
answers.targetDir = path.join(answers.baseDir);
answers.packageType = packageType;
createPackage(answers);
});
};
askPathQuestions();
}
// Todo: inject with webpack
program.version('2.0.7').description('OHIF CLI');
program
.command('create-extension')
.description('Create a new template extension')
.action(() => {
_createPackage('extension');
});
program
.command('create-mode')
.description('Create a new template Mode')
.action(() => {
_createPackage('mode');
});
program
.command('add-extension <packageName> [version]')
.description('Adds an ohif extension')
.action((packageName, version) => {
// change directory to viewer
process.chdir(viewerDirectory);
addExtension(packageName, version);
});
program
.command('remove-extension <packageName>')
.description('removes an ohif extension')
.action((packageName) => {
// change directory to viewer
process.chdir(viewerDirectory);
removeExtension(packageName);
});
program
.command('add-mode <packageName> [version]')
.description('Removes an ohif mode')
.action((packageName, version) => {
// change directory to viewer
process.chdir(viewerDirectory);
addMode(packageName, version);
});
program
.command('remove-mode <packageName>')
.description('Removes an ohif mode')
.action((packageName) => {
// change directory to viewer
process.chdir(viewerDirectory);
removeMode(packageName);
});
program
.command('link-extension <packageDir>')
.description(
'Links a local OHIF extension to the Viewer to be used for development'
)
.action((packageDir) => {
if (!fs.existsSync(packageDir)) {
console.log(
chalk.red(
'The extension directory does not exist, please provide a valid directory'
)
);
process.exit(1);
}
linkExtension(packageDir, { viewerDirectory });
});
program
.command('unlink-extension <extensionName>')
.description('Unlinks a local OHIF extension from the Viewer')
.action((extensionName) => {
unlinkExtension(extensionName, { viewerDirectory });
console.log(
chalk.green(
`Successfully unlinked extension ${extensionName} from the Viewer, don't forget to run yarn install --force`
)
);
});
program
.command('link-mode <packageDir>')
.description(
'Links a local OHIF mode to the Viewer to be used for development'
)
.action((packageDir) => {
if (!fs.existsSync(packageDir)) {
console.log(
chalk.red(
'The mode directory does not exist, please provide a valid directory'
)
);
process.exit(1);
}
linkMode(packageDir, { viewerDirectory });
});
program
.command('unlink-mode <modeName>')
.description('Unlinks a local OHIF mode from the Viewer')
.action((modeName) => {
unlinkMode(modeName, { viewerDirectory });
console.log(
chalk.green(
`Successfully unlinked mode ${modeName} from the Viewer, don't forget to run yarn install --force`
)
);
});
program
.command('list')
.description('List Added Extensions and Modes')
.action(() => {
const configPath = path.resolve(viewerDirectory, './pluginConfig.json');
listPlugins(configPath);
});
program
.command('search')
.option('-v, --verbose', 'Verbose output')
.description('Search NPM for the list of Modes and Extensions')
.action((options) => {
searchPlugins(options);
});
program.parse(process.argv);

View File

@ -0,0 +1,85 @@
import path from 'path';
function getPathQuestions(packageType) {
return [
{
type: 'input',
name: 'name',
message: `What is the name of your ${packageType}?`,
validate: (input) => {
if (!input) {
return 'Please enter a name';
}
return true;
},
default: `my-${packageType}`,
},
{
type: 'input',
name: 'baseDir',
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;
}
return true;
},
filter: (input, answers) => {
return path.resolve(input, answers.name);
},
},
{
type: 'confirm',
name: 'confirm',
message: `Please confirm the above path for generating the ${packageType} folder:`,
},
];
}
function getRepoQuestions(packageType) {
return [
{
type: 'confirm',
name: 'gitRepository',
message: 'Should it be a git repository?',
},
{
type: 'confirm',
name: 'prettier',
message: 'Should it follow same prettier rules as OHIF?',
},
{
type: 'input',
name: 'version',
message: `What is the version of your ${packageType}?`,
default: '0.0.1',
},
{
type: 'input',
name: 'description',
message: `What is the description of your ${packageType}?`,
default: '',
},
{
type: 'input',
name: 'author',
message: `Who is the author of your ${packageType}?`,
default: '',
},
{
type: 'input',
name: 'email',
message: 'What is your email address?',
default: '',
},
{
type: 'input',
name: 'license',
message: `What is the license of your ${packageType}?`,
default: 'MIT',
},
];
}
export { getPathQuestions, getRepoQuestions };

View File

@ -0,0 +1,104 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

View File

@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"printWidth": 80,
"proseWrap": "always",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}

View File

@ -0,0 +1,48 @@
const path = require('path');
const pkg = require('../package.json');
const outputFile = 'index.umd.js';
const rootDir = path.resolve(__dirname, '../');
const outputFolder = path.join(__dirname, '../dist');
const config = {
mode: 'production',
entry: rootDir + '/' + pkg.module,
devtool: 'inline-source-map',
output: {
path: outputFolder,
filename: outputFile,
library: pkg.name,
libraryTarget: 'umd',
umdNamedDefine: true,
globalObject: "typeof self !== 'undefined' ? self : this",
},
externals: [
{
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react',
},
},
],
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/,
resolve: {
extensions: ['.js', '.jsx'],
},
},
],
},
resolve: {
modules: [path.resolve('./node_modules'), path.resolve('./src')],
extensions: ['.json', '.js', '.jsx'],
},
};
module.exports = config;

View File

@ -0,0 +1,41 @@
module.exports = {
plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [
[
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
'@babel/preset-env',
{
modules: 'commonjs',
debug: false,
},
],
'@babel/preset-react',
],
plugins: [
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-regenerator',
'@babel/plugin-transform-runtime',
],
},
production: {
presets: [
// WebPack handles ES6 --> Target Syntax
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
development: {
presets: [
// WebPack handles ES6 --> Target Syntax
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
],
plugins: ['react-hot-loader/babel'],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
},
};

View File

@ -0,0 +1,58 @@
{
"repository": "OHIF/Viewers",
"keywords": ["ohif-extension"],
"main": "dist/index.umd.js",
"module": "src/index.js",
"engines": {
"node": ">=14",
"npm": ">=6",
"yarn": ">=1.18.0"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:dicom-pdf": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^3.0.0",
"@ohif/extension-default": "^1.0.1",
"@ohif/extension-cornerstone": "^3.0.0",
"@ohif/i18n": "^1.0.0",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^10.11.0",
"react-router": "^6.2.1",
"react-router-dom": "^6.2.1",
"webpack": "^5.50.0",
"webpack-merge": "^5.7.3"
},
"dependencies": {
"@babel/runtime": "7.7.6"
},
"devDependencies": {
"@babel/core": "^7.5.0",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-arrow-functions": "^7.2.0",
"@babel/plugin-transform-regenerator": "^7.4.5",
"@babel/plugin-transform-runtime": "^7.5.0",
"babel-plugin-inline-react-svg": "^2.0.1",
"@babel/preset-env": "^7.5.0",
"@babel/preset-react": "^7.0.0",
"babel-eslint": "^8.0.3",
"babel-loader": "^8.0.0-beta.4",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.0",
"cross-env": "^7.0.3",
"dotenv": "^14.1.0",
"eslint": "^5.0.1",
"eslint-loader": "^2.0.0",
"uglifyjs-webpack-plugin": "^1.2.7",
"webpack": "^4.12.2",
"webpack-cli": "^3.0.8"
}
}

View File

@ -0,0 +1,5 @@
import packageJson from '../package.json';
const id = packageJson.name;
export { id };

Some files were not shown because too many files have changed in this diff Show More