Compare commits

..
Author SHA1 Message Date
ohif-bot d8055e458f chore(release): publish [skip ci]
- @ohif/extension-cornerstone@1.5.0
 - @ohif/extension-vtk@0.53.5
 - @ohif/core@1.9.0
 - @ohif/ui@0.62.0
 - @ohif/viewer@2.8.0
2019-11-13 20:12:25 +00:00
Igor Octaviano 5c04e34c8f feat: expose UiNotifications service (#1172)
* playing around

* Change extension init and preRegistration signature

* Clear test code

* Update core index test

* Fix test

* Tricking the bug

* Renaming file back to trick some weird bug
2019-11-13 15:02:14 -05:00
Egor Lezhnin c59c5b3f14 feat: filter field for google api windows (#1170) 2019-11-13 09:38:49 -05:00
ohif-bot c4adcfd39e chore(release): publish [skip ci]
- @ohif/viewer@2.7.1
2019-11-12 12:23:55 +00:00
ladeirarodolfo 0fbaf95971 fix: 🐛 Fix for JS breaking on header (#1164) 2019-11-12 07:13:59 -05:00
fossabot a2a7e05e7d docs: Add license scan report and status (#1161)
Signed-off-by: fossabot <badges@fossa.io>
2019-11-11 21:43:24 -05:00
ohif-bot 9da8647c84 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.53.4
 - @ohif/core@1.8.0
 - @ohif/ui@0.61.0
 - @ohif/viewer@2.7.0
2019-11-12 02:17:40 +00:00
Igor Octaviano e62f5f8dd2 feat: 🎸 Update hotkeys and user preferences modal (#1135)
* feat: 🎸 Update hotkeys and user preferences modal

This feature fix incompatibility with existent hotkeys component and
adds user preferences modal back

Closes: #923

* Update preferences structure in store

* Hide window level section of user preferences

* Update modal to reflect current hotkey value

* Clone object with hotkeys before passing to manager

* CR Update: Extract hotkeys manager format code to manager

* Fix broken cypress test

* Use new modal provider

* Rename hotkeyDefinitions in hotkeyspreferences and use array as representation

* Update study test and remove unused styles
2019-11-11 21:03:36 -05:00
59 changed files with 650 additions and 334 deletions

No files matched your search

+4
View File
@@ -22,6 +22,7 @@
[![NPM downloads][npm-downloads-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url]
[![Pulls][docker-pulls-img]][docker-image-url] [![Pulls][docker-pulls-img]][docker-image-url]
[![MIT License][license-image]][license-url] [![MIT License][license-image]][license-url]
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FOHIF%2FViewers.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_shield)
[![Netlify Status][netlify-image]][netlify-url] [![Netlify Status][netlify-image]][netlify-url]
[![CircleCI][circleci-image]][circleci-url] [![CircleCI][circleci-image]][circleci-url]
@@ -324,3 +325,6 @@ MIT © [OHIF](https://github.com/OHIF)
[extension-vtk]: extensions/vtk/README.md [extension-vtk]: extensions/vtk/README.md
[vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk [vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FOHIF%2FViewers.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large)
+1 -1
View File
@@ -11,7 +11,7 @@ export default {
* LIFECYCLE HOOKS * LIFECYCLE HOOKS
*/ */
preRegistration(extensionConfiguration) {}, preRegistration({ serviceManager, configuration: extensionConfiguration }) {},
/** /**
* MODULE GETTERS * MODULE GETTERS
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.1...@ohif/extension-cornerstone@1.5.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.0...@ohif/extension-cornerstone@1.4.1) (2019-11-08) ## [1.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.4.0...@ohif/extension-cornerstone@1.4.1) (2019-11-08)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "1.4.1", "version": "1.5.0",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -24,8 +24,8 @@ export default {
* @param {object} [configuration={}] * @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools` * @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/ */
preRegistration(configuration = {}) { preRegistration({ serviceManager, configuration = {} }) {
init(configuration); init({ serviceManager, configuration });
}, },
getViewportModule() { getViewportModule() {
return OHIFCornerstoneViewport; return OHIFCornerstoneViewport;
+9 -7
View File
@@ -4,16 +4,18 @@ import csTools from 'cornerstone-tools';
import initCornerstoneTools from './initCornerstoneTools.js'; import initCornerstoneTools from './initCornerstoneTools.js';
import queryString from 'query-string'; import queryString from 'query-string';
function fallbackMetaDataProvider (type, imageId) { function fallbackMetaDataProvider(type, imageId) {
if (!imageId.includes('wado?requestType=WADO')) { if (!imageId.includes('wado?requestType=WADO')) {
return return;
} }
// If you call for an WADO-URI imageId and get no // If you call for an WADO-URI imageId and get no
// metadata, try reformatting to WADO-RS imageId // metadata, try reformatting to WADO-RS imageId
const qs = queryString.parse(imageId); const qs = queryString.parse(imageId);
const wadoRoot = window.store.getState().servers.servers[0].wadoRoot const wadoRoot = window.store.getState().servers.servers[0].wadoRoot;
const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${qs.seriesUID}/instances/${qs.objectUID}/frames/${qs.frame || 1}`; const wadoRsImageId = `wadors:${wadoRoot}/studies/${qs.studyUID}/series/${
qs.seriesUID
}/instances/${qs.objectUID}/frames/${qs.frame || 1}`;
return cornerstone.metaData.get(type, wadoRsImageId); return cornerstone.metaData.get(type, wadoRsImageId);
} }
@@ -21,13 +23,12 @@ function fallbackMetaDataProvider (type, imageId) {
// Add this fallback provider with a low priority so it is handled last // Add this fallback provider with a low priority so it is handled last
cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1); cornerstone.metaData.addProvider(fallbackMetaDataProvider, -1);
/** /**
* *
* @param {object} configuration * @param {object} configuration
* @param {Object|Array} configuration.csToolsConfig * @param {Object|Array} configuration.csToolsConfig
*/ */
export default function init(configuration = {}) { export default function init({ serviceManager, configuration = {} }) {
const { csToolsConfig } = configuration; const { csToolsConfig } = configuration;
const { StackManager } = OHIF.utils; const { StackManager } = OHIF.utils;
const metadataProvider = new OHIF.cornerstone.MetadataProvider(); const metadataProvider = new OHIF.cornerstone.MetadataProvider();
@@ -97,6 +98,7 @@ export default function init(configuration = {}) {
]; ];
tools.forEach(tool => csTools.addTool(tool)); tools.forEach(tool => csTools.addTool(tool));
csTools.setToolActive('Pan', { mouseButtonMask: 4 }); csTools.setToolActive('Pan', { mouseButtonMask: 4 });
csTools.setToolActive('Zoom', { mouseButtonMask: 2 }); csTools.setToolActive('Zoom', { mouseButtonMask: 2 });
csTools.setToolActive('Wwwc', { mouseButtonMask: 1 }); csTools.setToolActive('Wwwc', { mouseButtonMask: 1 });
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.53.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.4...@ohif/extension-vtk@0.53.5) (2019-11-13)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.3...@ohif/extension-vtk@0.53.4) (2019-11-12)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.2...@ohif/extension-vtk@0.53.3) (2019-11-11) ## [0.53.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.53.2...@ohif/extension-vtk@0.53.3) (2019-11-11)
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "0.53.3", "version": "0.53.5",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -52,8 +52,8 @@
"react-vtkjs-viewport": "^0.3.9" "react-vtkjs-viewport": "^0.3.9"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^1.7.1", "@ohif/core": "^1.9.0",
"@ohif/ui": "^0.60.1", "@ohif/ui": "^0.62.0",
"cornerstone-tools": "^4.0.9", "cornerstone-tools": "^4.0.9",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.6.1", "dcmjs": "^0.6.1",
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.8.0...@ohif/core@1.9.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.1...@ohif/core@1.8.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.0...@ohif/core@1.7.1) (2019-11-08) ## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.7.0...@ohif/core@1.7.1) (2019-11-08)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "1.7.1", "version": "1.9.0",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
+4 -2
View File
@@ -1,3 +1,4 @@
import cloneDeep from 'lodash.clonedeep';
import hotkeys from './hotkeys'; import hotkeys from './hotkeys';
import log from './../log.js'; import log from './../log.js';
@@ -51,10 +52,11 @@ export class HotkeysManager {
* @param {Boolean} [isDefaultDefinitions] * @param {Boolean} [isDefaultDefinitions]
*/ */
setHotkeys(hotkeyDefinitions, isDefaultDefinitions = false) { setHotkeys(hotkeyDefinitions, isDefaultDefinitions = false) {
hotkeyDefinitions.forEach(definition => this.registerHotkeys(definition)); const definitions = cloneDeep(hotkeyDefinitions);
definitions.forEach(definition => this.registerHotkeys(definition));
if (isDefaultDefinitions) { if (isDefaultDefinitions) {
this.hotkeyDefaults = hotkeyDefinitions; this.hotkeyDefaults = definitions;
} }
} }
@@ -2,12 +2,13 @@ import MODULE_TYPES from './MODULE_TYPES.js';
import log from './../log.js'; import log from './../log.js';
export default class ExtensionManager { export default class ExtensionManager {
constructor({ commandsManager }) { constructor({ commandsManager, servicesManager }) {
this.modules = {}; this.modules = {};
this.registeredExtensionIds = []; this.registeredExtensionIds = [];
this.moduleTypeNames = Object.values(MODULE_TYPES); this.moduleTypeNames = Object.values(MODULE_TYPES);
// //
this._commandsManager = commandsManager; this._commandsManager = commandsManager;
this._servicesManager = servicesManager;
this.moduleTypeNames.forEach(moduleType => { this.moduleTypeNames.forEach(moduleType => {
this.modules[moduleType] = []; this.modules[moduleType] = [];
@@ -66,7 +67,10 @@ export default class ExtensionManager {
// preRegistrationHook // preRegistrationHook
if (extension.preRegistration) { if (extension.preRegistration) {
extension.preRegistration(configuration); extension.preRegistration({
serviceManager: this._servicesManager,
configuration,
});
} }
// Register Modules // Register Modules
+9
View File
@@ -1,6 +1,7 @@
import './lib'; import './lib';
import { ExtensionManager, MODULE_TYPES } from './extensions'; import { ExtensionManager, MODULE_TYPES } from './extensions';
import { ServicesManager } from './services';
import classes, { CommandsManager, HotkeysManager } from './classes/'; import classes, { CommandsManager, HotkeysManager } from './classes/';
import DICOMWeb from './DICOMWeb'; import DICOMWeb from './DICOMWeb';
@@ -18,12 +19,15 @@ import ui from './ui';
import user from './user.js'; import user from './user.js';
import utils from './utils/'; import utils from './utils/';
import { createUiNotificationService } from './services';
const OHIF = { const OHIF = {
MODULE_TYPES, MODULE_TYPES,
// //
CommandsManager, CommandsManager,
ExtensionManager, ExtensionManager,
HotkeysManager, HotkeysManager,
ServicesManager,
// //
utils, utils,
studies, studies,
@@ -41,6 +45,8 @@ const OHIF = {
viewer: {}, viewer: {},
measurements, measurements,
hangingProtocols, hangingProtocols,
//
createUiNotificationService,
}; };
export { export {
@@ -49,6 +55,7 @@ export {
CommandsManager, CommandsManager,
ExtensionManager, ExtensionManager,
HotkeysManager, HotkeysManager,
ServicesManager,
// //
utils, utils,
studies, studies,
@@ -65,6 +72,8 @@ export {
DICOMWeb, DICOMWeb,
measurements, measurements,
hangingProtocols, hangingProtocols,
//
createUiNotificationService,
}; };
export { OHIF }; export { OHIF };
+3
View File
@@ -8,6 +8,9 @@ describe('Top level exports', () => {
'CommandsManager', 'CommandsManager',
'ExtensionManager', 'ExtensionManager',
'HotkeysManager', 'HotkeysManager',
'ServicesManager',
//
'createUiNotificationService',
// //
'utils', 'utils',
'studies', 'studies',
@@ -1,18 +1,15 @@
import cloneDeep from 'lodash.clonedeep'; import cloneDeep from 'lodash.clonedeep';
const defaultState = { const defaultState = {
// Top level key // First tab
viewer: { hotkeyDefinitions: [
// First tab // commandName, label, keys
hotKeysData: { // [{ zoom: { label: 'Zoom', keys: ['z'] }}]
// hotkeyName, label, keys, column ],
// zoom: { label: 'Zoom', command: 'Z', column: 0 }, // Second tab
}, windowLevelData: {
// Second tab // order, description, window (int), level (int)
windowLevelData: { // 0: { description: 'Soft tissue', window: '', level: '' },
// order, description, window (int), level (int)
// 0: { description: 'Soft tissue', window: '', level: '' },
},
}, },
}; };
@@ -0,0 +1,9 @@
export default class ServicesManager {
constructor() {
this.services = {};
}
register(service) {
this.services[service.name] = service;
}
}
@@ -0,0 +1,84 @@
/**
* A UI Notification
*
* @typedef {Object} Notification
* @property {string} title -
* @property {string} message -
* @property {number} [duration=5000] - in ms
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
* @property {string} [type="info"] - "info" | "error" | "warning" | "success"
* @property {boolean} [autoClose=true]
*/
const uiNotificationServicePublicApi = {
name: 'UINotificationService',
hide,
show,
setServiceImplementation,
};
const uiNotificationServiceImplementation = {
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
_show: () => console.warn('show() NOT IMPLEMENTED'),
};
function createUiNotificationService() {
return uiNotificationServicePublicApi;
}
/**
* Create and show a new UI notification; returns the
* ID of the created notification.
*
* @param {Notification} notification { title, message, duration, position, type, autoClose}
* @returns {number} id
*/
function show({
title,
message,
duration = 5000,
position = 'bottomRight',
type = 'info',
autoClose = true,
}) {
return uiNotificationServiceImplementation._show({
title,
message,
duration,
position,
type,
autoClose,
});
}
/**
* Hides/dismisses the notification, if currently shown
*
* @param {number} id - id of the notification to hide/dismiss
* @returns undefined
*/
function hide(id) {
return uiNotificationServiceImplementation._hide({ id });
}
/**
*
*
* @param {*} {
* hide: hideImplementation,
* show: showImplementation,
* }
*/
function setServiceImplementation({
hide: hideImplementation,
show: showImplementation,
}) {
if (hideImplementation) {
uiNotificationServiceImplementation._hide = hideImplementation;
}
if (showImplementation) {
uiNotificationServiceImplementation._show = showImplementation;
}
}
export default createUiNotificationService;
+4
View File
@@ -0,0 +1,4 @@
import ServicesManager from './ServicesManager.js';
import createUiNotificationService from './UINotificationService';
export { createUiNotificationService, ServicesManager };
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.62.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.61.0...@ohif/ui@0.62.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
# [0.61.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.1...@ohif/ui@0.61.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [0.60.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.0...@ohif/ui@0.60.1) (2019-11-08) ## [0.60.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.60.0...@ohif/ui@0.60.1) (2019-11-08)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "0.60.1", "version": "0.62.0",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -6,8 +6,8 @@ import { TableList, TableListItem } from './tableList';
import { import {
AboutContent, AboutContent,
UserPreferences, UserPreferences,
UserPreferencesModal, UserPreferencesForm,
} from './userPreferencesModal'; } from './userPreferencesForm';
import { Checkbox } from './checkbox'; import { Checkbox } from './checkbox';
import { CineDialog } from './cineDialog'; import { CineDialog } from './cineDialog';
@@ -52,6 +52,6 @@ export {
Tooltip, Tooltip,
AboutContent, AboutContent,
UserPreferences, UserPreferences,
UserPreferencesModal, UserPreferencesForm,
OHIFModal, OHIFModal,
}; };
@@ -10,31 +10,14 @@ import PropTypes from 'prop-types';
export class HotKeysPreferences extends Component { export class HotKeysPreferences extends Component {
static propTypes = { static propTypes = {
hotKeysData: PropTypes.objectOf( hotkeyDefinitions: PropTypes.array.isRequired,
PropTypes.shape({
keys: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.string.isRequired,
})
).isRequired,
onChange: PropTypes.func,
}; };
constructor(props) { constructor(props) {
super(props); super(props);
const hotkeyCommands = Object.keys(this.props.hotKeysData);
const localHotKeys = hotkeyCommands.map(commandName => {
const definition = this.props.hotKeysData[commandName];
return {
commandName,
keys: definition.keys,
label: definition.label,
};
});
this.state = { this.state = {
hotKeys: localHotKeys, hotKeys: this.props.hotkeyDefinitions,
errorMessages: {}, errorMessages: {},
}; };
@@ -53,24 +36,28 @@ export class HotKeysPreferences extends Component {
const { ctrlKey, altKey, shiftKey } = keyDownEvent; const { ctrlKey, altKey, shiftKey } = keyDownEvent;
if (ctrlKey && !altKey) { if (ctrlKey && !altKey) {
keysPressedArray.push('CTRL'); keysPressedArray.push('ctrl');
} }
if (shiftKey && !altKey) { if (shiftKey && !altKey) {
keysPressedArray.push('SHIFT'); keysPressedArray.push('shift');
} }
if (altKey && !ctrlKey) { if (altKey && !ctrlKey) {
keysPressedArray.push('ALT'); keysPressedArray.push('alt');
} }
return keysPressedArray; return keysPressedArray;
} }
getConflictingCommand(currentToolKey, hotKeyCommand) { getConflictingCommand(currentCommandName, currentHotKeys) {
return Object.keys(this.state.hotKeys).find(tool => { return this.state.hotKeys.find((tool, index) => {
const value = this.state.hotKeys[tool].command; const toolHotKeys = tool.keys[0];
return value && value === hotKeyCommand && tool !== currentToolKey; return (
toolHotKeys &&
toolHotKeys === currentHotKeys &&
tool.commandName !== currentCommandName
);
}); });
} }
@@ -89,7 +76,7 @@ export class HotKeysPreferences extends Component {
specialKeyName || specialKeyName ||
keyDownEvent.key || keyDownEvent.key ||
String.fromCharCode(keyDownEvent.keyCode); String.fromCharCode(keyDownEvent.keyCode);
pressedKeys.push(keyName.toUpperCase()); pressedKeys.push(keyName);
} }
this.updateHotKeysState(commandName, pressedKeys.join('+')); this.updateHotKeysState(commandName, pressedKeys.join('+'));
@@ -136,17 +123,17 @@ export class HotKeysPreferences extends Component {
const hotKey = this.state.hotKeys[hotKeyIndex]; const hotKey = this.state.hotKeys[hotKeyIndex];
const keys = hotKey.keys[0]; const keys = hotKey.keys[0];
const pressedKeys = keys.split('+'); const pressedKeys = keys.split('+');
const lastPressedKey = pressedKeys[pressedKeys.length - 1].toUpperCase(); const lastPressedKey = pressedKeys[pressedKeys.length - 1];
// clear the prior errors // clear the prior errors
this.setState({ errorMessages: {} }, () => { this.setState({ errorMessages: {} }, () => {
// Check if it has a valid modifier // Check if it has a valid modifier
const isModifier = ['CTRL', 'ALT', 'SHIFT'].includes(lastPressedKey); const isModifier = ['ctrl', 'alt', 'shift'].includes(lastPressedKey);
if (isModifier) { if (isModifier) {
this.updateHotKeysState(commandName, ''); this.updateHotKeysState(commandName, '');
this.updateErrorsState( this.updateErrorsState(
commandName, commandName,
"It's not possible to define only modifier keys (CTRL, ALT and SHIFT) as a shortcut" "It's not possible to define only modifier keys (ctrl, alt and shift) as a shortcut"
); );
return; return;
} }
@@ -154,21 +141,13 @@ export class HotKeysPreferences extends Component {
/* /*
* Check if it has some conflict * Check if it has some conflict
*/ */
const conflictedCommandKey = this.getConflictingCommand( const conflictedCommand = this.getConflictingCommand(commandName, keys);
commandName, if (conflictedCommand) {
keys this.updateHotKeysState(commandName, '');
);
if (conflictedCommandKey) {
const conflictedCommand = this.state.hotKeys[conflictedCommandKey];
this.updateErrorsState( this.updateErrorsState(
commandName, commandName,
`"${conflictedCommand.label}" is already using the "${ `"${conflictedCommand.label}" is already using the "${keys}" shortcut.`
conflictedCommand.command
}" shortcut.`
); );
this.updateErrorsState(conflictedCommandKey, '');
this.updateHotKeysState(commandName, '');
return; return;
} }
@@ -177,8 +156,7 @@ export class HotKeysPreferences extends Component {
*/ */
const modifierCommand = pressedKeys const modifierCommand = pressedKeys
.slice(0, pressedKeys.length - 1) .slice(0, pressedKeys.length - 1)
.join('+') .join('+');
.toUpperCase();
const disallowedCombination = disallowedCombinations[modifierCommand]; const disallowedCombination = disallowedCombinations[modifierCommand];
const hasDisallowedCombinations = disallowedCombination const hasDisallowedCombinations = disallowedCombination
@@ -189,7 +167,7 @@ export class HotKeysPreferences extends Component {
this.updateHotKeysState(commandName, ''); this.updateHotKeysState(commandName, '');
this.updateErrorsState( this.updateErrorsState(
commandName, commandName,
"It's not possible to define only modifier keys (CTRL, ALT and SHIFT) as a shortcut" `"${pressedKeys.join('+')}" shortcut combination is not allowed`
); );
return; return;
} }
@@ -8,14 +8,14 @@ import './UserPreferences.styl';
export class UserPreferences extends Component { export class UserPreferences extends Component {
static defaultProps = { static defaultProps = {
hotKeysData: {}, hotkeyDefinitions: [],
windowLevelData: {}, windowLevelData: {},
generalData: {}, generalData: {},
}; };
// TODO: Make this more generic. Tabs should not be restricted to these entries // TODO: Make this more generic. Tabs should not be restricted to these entries
static propTypes = { static propTypes = {
hotKeysData: PropTypes.object.isRequired, hotkeyDefinitions: PropTypes.array.isRequired,
windowLevelData: PropTypes.object.isRequired, windowLevelData: PropTypes.object.isRequired,
generalData: PropTypes.object.isRequired, generalData: PropTypes.object.isRequired,
}; };
@@ -32,7 +32,9 @@ export class UserPreferences extends Component {
return ( return (
<form className="form-themed themed"> <form className="form-themed themed">
<div className="form-content"> <div className="form-content">
<HotKeysPreferences hotKeysData={this.props.hotKeysData} /> <HotKeysPreferences
hotkeyDefinitions={this.props.hotkeyDefinitions}
/>
</div> </div>
</form> </form>
); );
@@ -66,8 +68,8 @@ export class UserPreferences extends Component {
switch (tabIndex) { switch (tabIndex) {
case 0: case 0:
return this.renderHotkeysTab(); return this.renderHotkeysTab();
case 1: /* case 1:
return this.renderWindowLevelTab(); return this.renderWindowLevelTab(); */
case 2: case 2:
return this.renderGeneralTab(); return this.renderGeneralTab();
@@ -93,14 +95,16 @@ export class UserPreferences extends Component {
> >
<button>Hotkeys</button> <button>Hotkeys</button>
</li> </li>
<li {false && (
onClick={() => { <li
this.tabClick(1); onClick={() => {
}} this.tabClick(1);
className={this.getTabClass(1)} }}
> className={this.getTabClass(1)}
<button>Window Level</button> >
</li> <button>Window Level</button>
</li>
)}
<li <li
onClick={() => { onClick={() => {
this.tabClick(2); this.tabClick(2);
@@ -0,0 +1,86 @@
import './UserPreferencesForm.styl';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from '../../utils/LanguageProvider';
import cloneDeep from 'lodash.clonedeep';
import isEqual from 'lodash.isequal';
import { UserPreferences } from './UserPreferences';
class UserPreferencesForm extends Component {
// TODO: Make this component more generic to allow things other than W/L and hotkeys...
static propTypes = {
onClose: PropTypes.func,
onSave: PropTypes.func,
onResetToDefaults: PropTypes.func,
windowLevelData: PropTypes.object,
hotkeyDefinitions: PropTypes.array,
t: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
windowLevelData: cloneDeep(props.windowLevelData),
hotkeyDefinitions: cloneDeep(props.hotkeyDefinitions),
};
}
save = () => {
this.props.onSave({
windowLevelData: this.state.windowLevelData,
hotkeyDefinitions: this.state.hotkeyDefinitions,
});
};
componentDidUpdate(prev, next) {
const newStateData = {};
if (!isEqual(prev.windowLevelData, next.windowLevelData)) {
newStateData.windowLevelData = prev.windowLevelData;
}
if (!isEqual(prev.hotkeyDefinitions, next.hotkeyDefinitions)) {
newStateData.hotkeyDefinitions = prev.hotkeyDefinitions;
}
if (newStateData.hotkeyDefinitions || newStateData.windowLevelData) {
this.setState(newStateData);
}
}
render() {
return (
<div className="UserPreferencesForm">
<UserPreferences
windowLevelData={this.state.windowLevelData}
hotkeyDefinitions={this.state.hotkeyDefinitions}
/>
<div className="footer">
<button
className="btn btn-danger pull-left"
onClick={this.props.onResetToDefaults}
>
{this.props.t('Reset to Defaults')}
</button>
<div>
<div onClick={this.props.onClose} className="btn btn-default">
{this.props.t('Cancel')}
</div>
<button className="btn btn-primary" onClick={this.save}>
{this.props.t('Save')}
</button>
</div>
</div>
</div>
);
}
}
const connectedComponent = withTranslation('UserPreferencesForm')(
UserPreferencesForm
);
export { connectedComponent as UserPreferencesForm };
export default connectedComponent;
@@ -12,6 +12,13 @@
text-shadow: 0 1px 0 #fff; text-shadow: 0 1px 0 #fff;
opacity: .2; opacity: .2;
.ModalHeader .UserPreferencesForm
ol, ul .footer
margin-top: 0; display: flex
flex-direction: row
padding-bottom: 20px
justify-content: space-between
div
button:last-child
margin-left: 10px
@@ -1,18 +1,18 @@
--- ---
name: User Preferences Modal name: User Preferences Form
menu: Components menu: Components
route: /components/user-preferences-modal route: /components/user-preferences-form
--- ---
import { Playground, Props } from 'docz' import { Playground, Props } from 'docz'
import { State } from 'react-powerplug' import { State } from 'react-powerplug'
import { UserPreferencesModal } from './../index.js' import { UserPreferencesForm } from './../index.js'
import NameSpace from '../../../__docs__/NameSpace' import NameSpace from '../../../__docs__/NameSpace'
// //
import windowLevelDefaults from './windowLevelDefaults.js' import windowLevelDefaults from './windowLevelDefaults.js'
import hotkeyDefaults from './hotkeyDefaults.js' import hotkeyDefaults from './hotkeyDefaults.js'
# User Preferences Modal # User Preferences Form
## Basic usage ## Basic usage
@@ -20,7 +20,7 @@ import hotkeyDefaults from './hotkeyDefaults.js'
<State initial={{ <State initial={{
isOpen: false, isOpen: false,
windowLevelData: windowLevelDefaults, windowLevelData: windowLevelDefaults,
hotKeysData: hotkeyDefaults, hotkeyDefinitions: hotkeyDefaults,
}}> }}>
{({ state, setState }) => ( {({ state, setState }) => (
@@ -32,7 +32,7 @@ import hotkeyDefaults from './hotkeyDefaults.js'
> >
Open user preferences Open user preferences
</button> </button>
<UserPreferencesModal <UserPreferencesForm
{...state} {...state}
onCancel={() => setState({ isOpen: false })} onCancel={() => setState({ isOpen: false })}
onSave={() => alert('on save')} onSave={() => alert('on save')}
@@ -47,8 +47,8 @@ import hotkeyDefaults from './hotkeyDefaults.js'
## API ## API
<Props of={UserPreferencesModal} /> <Props of={UserPreferencesForm} />
## Translation Namespace ## Translation Namespace
<NameSpace name="UserPreferencesModal" /> <NameSpace name="UserPreferencesForm" />
@@ -4,38 +4,38 @@ const range = (start, end) => {
export const disallowedCombinations = { export const disallowedCombinations = {
'': [], '': [],
ALT: ['SPACE'], alt: ['space'],
SHIFT: [], shift: [],
CTRL: [ ctrl: [
'F4', 'f4',
'F5', 'f5',
'F11', 'f11',
'W', 'w',
'R', 'r',
'T', 't',
'O', 'o',
'P', 'p',
'A', 'a',
'D', 'd',
'F', 'f',
'G', 'g',
'H', 'h',
'J', 'j',
'L', 'l',
'Z', 'z',
'X', 'x',
'C', 'c',
'V', 'v',
'B', 'b',
'N', 'n',
'PAGEDOWN', 'pagedown',
'PAGEUP', 'pageup',
], ],
'CTRL+SHIFT': ['Q', 'W', 'R', 'T', 'P', 'A', 'H', 'V', 'B', 'N'], 'ctrl+shift': ['q', 'w', 'r', 't', 'p', 'a', 'h', 'v', 'b', 'n'],
}; };
export const allowedKeys = [ export const allowedKeys = [
...[8, 13, 27, 32, 46], // BACKSPACE, ENTER, ESCAPE, SPACE, DELETE ...[8, 13, 27, 32, 46], // backspace, enter, escape, space, delete
...[12, 106, 107, 109, 110, 111], // Numpad keys ...[12, 106, 107, 109, 110, 111], // Numpad keys
...range(218, 220), // [\] ...range(218, 220), // [\]
...range(185, 190), // ;=,-./ ...range(185, 190), // ;=,-./
@@ -1,4 +1,4 @@
export { UserPreferences } from './UserPreferences.js'; export { UserPreferences } from './UserPreferences.js';
export { AboutContent } from '../content/aboutContent/AboutContent.js'; export { AboutContent } from '../content/aboutContent/AboutContent.js';
export { UserPreferencesModal } from './UserPreferencesModal.js'; export { UserPreferencesForm } from './UserPreferencesForm.js';
export { GeneralPreferences } from './GeneralPreferences.js'; export { GeneralPreferences } from './GeneralPreferences.js';
@@ -1,107 +0,0 @@
import './UserPreferencesModal.styl';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-bootstrap-modal';
import { withTranslation } from '../../utils/LanguageProvider';
import 'react-bootstrap-modal/lib/css/rbm-patch.css';
import cloneDeep from 'lodash.clonedeep';
import isEqual from 'lodash.isequal';
import { UserPreferences } from './UserPreferences';
// TODO: Is this the only component importing these?
import './../../design/styles/common/modal.styl';
class UserPreferencesModal extends Component {
// TODO: Make this component more generic to allow things other than W/L and hotkeys...
static propTypes = {
isOpen: PropTypes.bool.isRequired,
onCancel: PropTypes.func,
onSave: PropTypes.func,
onResetToDefaults: PropTypes.func,
windowLevelData: PropTypes.object,
hotKeysData: PropTypes.object,
t: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
windowLevelData: cloneDeep(props.windowLevelData),
hotKeysData: cloneDeep(props.hotKeysData),
};
}
static defaultProps = {
isOpen: false,
};
save = () => {
this.props.onSave({
windowLevelData: this.state.windowLevelData,
hotKeysData: this.state.hotKeysData,
});
};
componentDidUpdate(prev, next) {
const newStateData = {};
if (!isEqual(prev.windowLevelData, next.windowLevelData)) {
newStateData.windowLevelData = prev.windowLevelData;
}
if (!isEqual(prev.hotKeysData, next.hotKeysData)) {
newStateData.hotKeysData = prev.hotKeysData;
}
if (newStateData.hotKeysData || newStateData.windowLevelData) {
this.setState(newStateData);
}
}
render() {
return (
<Modal
show={this.props.isOpen}
onHide={this.props.onCancel}
aria-labelledby="ModalHeader"
className="ModalHeader modal fade themed in"
backdrop={false}
large={true}
keyboard={false}
>
<Modal.Header closeButton>
<Modal.Title>{this.props.t('User Preferences')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<UserPreferences
windowLevelData={this.state.windowLevelData}
hotKeysData={this.state.hotKeysData}
/>
</Modal.Body>
<Modal.Footer>
<button
className="btn btn-danger pull-left"
onClick={this.props.onResetToDefaults}
>
{this.props.t('Reset to Defaults')}
</button>
<Modal.Dismiss className="btn btn-default">
{this.props.t('Cancel')}
</Modal.Dismiss>
<button className="btn btn-primary" onClick={this.save}>
{this.props.t('Save')}
</button>
</Modal.Footer>
</Modal>
);
}
}
const connectedComponent = withTranslation('UserPreferencesModal')(
UserPreferencesModal
);
export { connectedComponent as UserPreferencesModal };
export default connectedComponent;
+2 -2
View File
@@ -24,7 +24,7 @@ import {
Tooltip, Tooltip,
AboutContent, AboutContent,
UserPreferences, UserPreferences,
UserPreferencesModal, UserPreferencesForm,
OHIFModal, OHIFModal,
} from './components'; } from './components';
import { useDebounce, useMedia } from './hooks'; import { useDebounce, useMedia } from './hooks';
@@ -101,7 +101,7 @@ export {
Tooltip, Tooltip,
AboutContent, AboutContent,
UserPreferences, UserPreferences,
UserPreferencesModal, UserPreferencesForm,
ViewerbaseDragDropContext, ViewerbaseDragDropContext,
SnackbarProvider, SnackbarProvider,
useSnackbarContext, useSnackbarContext,
+16 -6
View File
@@ -1,4 +1,10 @@
import React, { useState, createContext, useContext } from 'react'; import React, {
useState,
createContext,
useContext,
useCallback,
useEffect,
} from 'react';
import SnackbarContainer from '../components/snackbar/SnackbarContainer'; import SnackbarContainer from '../components/snackbar/SnackbarContainer';
import SnackbarTypes from '../components/snackbar/SnackbarTypes'; import SnackbarTypes from '../components/snackbar/SnackbarTypes';
@@ -6,7 +12,7 @@ const SnackbarContext = createContext(null);
export const useSnackbarContext = () => useContext(SnackbarContext); export const useSnackbarContext = () => useContext(SnackbarContext);
const SnackbarProvider = ({ children }) => { const SnackbarProvider = ({ children, service }) => {
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
title: '', title: '',
message: '', message: '',
@@ -19,7 +25,11 @@ const SnackbarProvider = ({ children }) => {
const [count, setCount] = useState(1); const [count, setCount] = useState(1);
const [snackbarItems, setSnackbarItems] = useState([]); const [snackbarItems, setSnackbarItems] = useState([]);
const show = options => { useEffect(() => {
service.setServiceImplementation({ hide, show });
}, [service, hide, show]);
const show = useCallback(options => {
if (!options || (!options.title && !options.message)) { if (!options || (!options.title && !options.message)) {
console.warn( console.warn(
'Snackbar cannot be rendered without required parameters: title | message' 'Snackbar cannot be rendered without required parameters: title | message'
@@ -37,9 +47,9 @@ const SnackbarProvider = ({ children }) => {
setSnackbarItems(state => [...state, newItem]); setSnackbarItems(state => [...state, newItem]);
setCount(count + 1); setCount(count + 1);
}; });
const hide = id => { const hide = useCallback(id => {
const hideItem = items => { const hideItem = items => {
const newItems = items.map(item => { const newItems = items.map(item => {
if (item.id === id) { if (item.id === id) {
@@ -57,7 +67,7 @@ const SnackbarProvider = ({ children }) => {
setTimeout(() => { setTimeout(() => {
setSnackbarItems(state => [...state.filter(item => item.id !== id)]); setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
}, 1000); }, 1000);
}; });
const hideAll = () => { const hideAll = () => {
// reset count // reset count
+34
View File
@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.7.1...@ohif/viewer@2.8.0) (2019-11-13)
### Features
* expose UiNotifications service ([#1172](https://github.com/OHIF/Viewers/issues/1172)) ([5c04e34](https://github.com/OHIF/Viewers/commit/5c04e34c8fb2394ab7acd9eb4f2ab12afeb2f255))
* filter field for google api windows ([#1170](https://github.com/OHIF/Viewers/issues/1170)) ([c59c5b3](https://github.com/OHIF/Viewers/commit/c59c5b3f14d44f1c06aa396125a1f4caaa431c25))
## [2.7.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.7.0...@ohif/viewer@2.7.1) (2019-11-12)
### Bug Fixes
* 🐛 Fix for JS breaking on header ([#1164](https://github.com/OHIF/Viewers/issues/1164)) ([0fbaf95](https://github.com/OHIF/Viewers/commit/0fbaf95971dc0b3a671e1f586a876d9019e860ed))
# [2.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.4...@ohif/viewer@2.7.0) (2019-11-12)
### Features
* 🎸 Update hotkeys and user preferences modal ([#1135](https://github.com/OHIF/Viewers/issues/1135)) ([e62f5f8](https://github.com/OHIF/Viewers/commit/e62f5f8dd28ab363f23671cd21cee115abb870ff)), closes [#923](https://github.com/OHIF/Viewers/issues/923)
## [2.6.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.3...@ohif/viewer@2.6.4) (2019-11-11) ## [2.6.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@2.6.3...@ohif/viewer@2.6.4) (2019-11-11)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
@@ -267,8 +267,12 @@ describe('OHIF Study Viewer Page', function() {
}); });
it('opens About modal and verify the displayed information', function() { it('opens About modal and verify the displayed information', function() {
cy.get('[data-cy="options-menu"]').click(); cy.get('[data-cy="options-menu"]')
cy.get('[data-cy="about-item-menu"]').click(); .first()
.click();
cy.get('[data-cy="about-item-menu"]')
.first()
.click();
cy.get('.modal-content') cy.get('.modal-content')
.as('aboutOverlay') .as('aboutOverlay')
.should('be.visible'); .should('be.visible');
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "2.6.4", "version": "2.8.0",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -45,14 +45,14 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ohif/core": "^1.7.1", "@ohif/core": "^1.9.0",
"@ohif/extension-cornerstone": "^2.0.0", "@ohif/extension-cornerstone": "^2.0.0",
"@ohif/extension-dicom-html": "^1.0.1", "@ohif/extension-dicom-html": "^1.0.1",
"@ohif/extension-dicom-microscopy": "^0.50.6", "@ohif/extension-dicom-microscopy": "^0.50.6",
"@ohif/extension-dicom-pdf": "^1.0.0", "@ohif/extension-dicom-pdf": "^1.0.0",
"@ohif/extension-vtk": "^0.53.3", "@ohif/extension-vtk": "^0.53.5",
"@ohif/i18n": "^0.52.0", "@ohif/i18n": "^0.52.0",
"@ohif/ui": "^0.60.1", "@ohif/ui": "^0.62.0",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
+19 -4
View File
@@ -6,7 +6,9 @@ import './config';
import { import {
CommandsManager, CommandsManager,
ExtensionManager, ExtensionManager,
ServicesManager,
HotkeysManager, HotkeysManager,
createUiNotificationService,
utils, utils,
} from '@ohif/core'; } from '@ohif/core';
import React, { Component } from 'react'; import React, { Component } from 'react';
@@ -41,9 +43,16 @@ const commandsManagerConfig = {
getActiveContexts: () => getActiveContexts(store.getState()), getActiveContexts: () => getActiveContexts(store.getState()),
}; };
// Services
const UINotificationService = createUiNotificationService();
const commandsManager = new CommandsManager(commandsManagerConfig); const commandsManager = new CommandsManager(commandsManagerConfig);
const hotkeysManager = new HotkeysManager(commandsManager); const hotkeysManager = new HotkeysManager(commandsManager);
const extensionManager = new ExtensionManager({ commandsManager }); const servicesManager = new ServicesManager();
const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
});
// ~~~~ END APP SETUP // ~~~~ END APP SETUP
// TODO[react] Use a provider when the whole tree is React // TODO[react] Use a provider when the whole tree is React
@@ -76,9 +85,11 @@ class App extends Component {
super(props); super(props);
this._appConfig = props; this._appConfig = props;
const { servers, extensions, hotkeys, oidc } = props; const { servers, extensions, hotkeys, oidc } = props;
this.initUserManager(oidc); this.initUserManager(oidc);
_initServices([UINotificationService]);
_initExtensions(extensions, hotkeys); _initExtensions(extensions, hotkeys);
_initServers(servers); _initServers(servers);
initWebWorkers(); initWebWorkers();
@@ -100,7 +111,7 @@ class App extends Component {
<UserManagerContext.Provider value={userManager}> <UserManagerContext.Provider value={userManager}>
<Router basename={routerBasename}> <Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}> <WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider> <SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal}> <ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer userManager={userManager} /> <OHIFStandaloneViewer userManager={userManager} />
</ModalProvider> </ModalProvider>
@@ -121,7 +132,7 @@ class App extends Component {
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<Router basename={routerBasename}> <Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}> <WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider> <SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal}> <ModalProvider modal={OHIFModal}>
<OHIFStandaloneViewer /> <OHIFStandaloneViewer />
</ModalProvider> </ModalProvider>
@@ -168,6 +179,10 @@ class App extends Component {
} }
} }
function _initServices(services) {
services.forEach(service => servicesManager.register(service));
}
/** /**
* @param * @param
*/ */
@@ -214,4 +229,4 @@ function _makeAbsoluteIfNecessary(url, base_url) {
const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App; const ExportedApp = process.env.NODE_ENV === 'development' ? hot(App) : App;
export default ExportedApp; export default ExportedApp;
export { commandsManager, extensionManager, hotkeysManager }; export { commandsManager, extensionManager, hotkeysManager, servicesManager };
@@ -7,8 +7,8 @@ export default {
*/ */
id: 'measurements-table', id: 'measurements-table',
preRegistration(configuration = {}) { preRegistration({ serviceManager, configuration = {} }) {
init(configuration); init({ serviceManager, configuration });
}, },
getPanelModule() { getPanelModule() {
return { return {
@@ -34,7 +34,7 @@ const MEASUREMENT_ACTION_MAP = {
* @export * @export
* @param {*} configuration * @param {*} configuration
*/ */
export default function init(configuration) { export default function init({ serviceManager, configuration = {} }) {
// If these tools were already added by a different extension, we want to replace // If these tools were already added by a different extension, we want to replace
// them with the same tools that have an alternative configuration. By passing in // them with the same tools that have an alternative configuration. By passing in
// our custom `getMeasurementLocationCallback`, we can... // our custom `getMeasurementLocationCallback`, we can...
@@ -4,6 +4,10 @@
height: var(--top-bar-height); height: var(--top-bar-height);
} }
.dd-item {
width: 100%;
}
/* Home Page */ /* Home Page */
.entry-header.header-big { .entry-header.header-big {
background: rgba(21, 25, 30, 0.7); background: rgba(21, 25, 30, 0.7);
+14 -29
View File
@@ -3,13 +3,11 @@ import { Link, withRouter } from 'react-router-dom';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Dropdown } from '@ohif/ui'; import ConnectedUserPreferencesForm from '../../connectedComponents/ConnectedUserPreferencesForm';
import { AboutContent } from '@ohif/ui'; import { Dropdown, AboutContent, withModal } from '@ohif/ui';
import { withModal } from '@ohif/ui';
import OHIFLogo from '../OHIFLogo/OHIFLogo.js'; import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
import { hotkeysManager } from './../../App.js';
import './Header.css'; import './Header.css';
// Context // Context
import AppContext from './../../context/AppContext'; import AppContext from './../../context/AppContext';
@@ -30,23 +28,9 @@ class Header extends Component {
children: OHIFLogo(), children: OHIFLogo(),
}; };
// onSave: data => {
// const contextName = store.getState().commandContext.context;
// const preferences = cloneDeep(store.getState().preferences);
// preferences[contextName] = data;
// dispatch(setUserPreferences(preferences));
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
// },
// onResetToDefaults: () => {
// dispatch(setUserPreferences());
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys();
// },
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { isUserPreferencesOpen: false, isOpen: false }; this.state = { isOpen: false };
this.loadOptions(); this.loadOptions();
} }
@@ -68,6 +52,16 @@ class Header extends Component {
customClassName: 'AboutContent', customClassName: 'AboutContent',
}), }),
}, },
{
title: 'Preferences ',
icon: {
name: 'user',
},
onClick: () =>
show(ConnectedUserPreferencesForm, {
title: t('User Preferences'),
}),
},
]; ];
if (user && userManager) { if (user && userManager) {
@@ -77,15 +71,6 @@ class Header extends Component {
onClick: () => userManager.signoutRedirect(), onClick: () => userManager.signoutRedirect(),
}); });
} }
this.hotKeysData = hotkeysManager.hotkeyDefinitions;
}
onUserPreferencesSave({ windowLevelData, hotKeysData }) {
// console.log(windowLevelData);
// console.log(hotKeysData);
// TODO: Update hotkeysManager
// TODO: reset `this.hotKeysData`
} }
// ANTD -- Hamburger, Drawer, Menu // ANTD -- Hamburger, Drawer, Menu
@@ -1,10 +1,19 @@
import Header from '../components/Header/Header.js'; import Header from '../components/Header/Header.js';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { hotkeysManager } from '../App.js';
const mapStateToProps = state => { const mapStateToProps = state => {
const { hotkeyDefinitions = [] } = state.preferences || {};
const _hotkeyDefinitions =
hotkeyDefinitions.length > 0
? hotkeyDefinitions
: hotkeysManager.hotkeyDefaults;
hotkeysManager.setHotkeys(_hotkeyDefinitions);
return { return {
user: state.oidc && state.oidc.user, user: state.oidc && state.oidc.user,
isOpen: state.ui.userPreferencesModalOpen,
}; };
}; };
@@ -0,0 +1,41 @@
import { connect } from 'react-redux';
import { UserPreferencesForm } from '@ohif/ui';
import OHIF from '@ohif/core';
import { hotkeysManager } from '../App.js';
const { setUserPreferences } = OHIF.redux.actions;
const mapStateToProps = (state, ownProps) => {
const hotkeyDefinitions =
state.preferences.hotkeyDefinitions.length > 0
? state.preferences.hotkeyDefinitions
: hotkeysManager.hotkeyDefaults;
hotkeysManager.setHotkeys(hotkeyDefinitions);
return {
onClose: ownProps.hide,
windowLevelData: state.preferences ? state.preferences.windowLevelData : {},
hotkeyDefinitions,
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onSave: ({ windowLevelData, hotkeyDefinitions }) => {
hotkeysManager.setHotkeys(hotkeyDefinitions);
ownProps.hide();
dispatch(setUserPreferences({ windowLevelData, hotkeyDefinitions }));
},
onResetToDefaults: () => {
hotkeysManager.restoreDefaultBindings();
ownProps.hide();
dispatch(setUserPreferences());
},
};
};
const ConnectedUserPreferencesForm = connect(
mapStateToProps,
mapDispatchToProps
)(UserPreferencesForm);
export default ConnectedUserPreferencesForm;
@@ -9,6 +9,7 @@ export default class DatasetPicker extends Component {
error: null, error: null,
loading: true, loading: true,
datasets: [], datasets: [],
filterStr: ""
}; };
static propTypes = { static propTypes = {
@@ -41,15 +42,23 @@ export default class DatasetPicker extends Component {
} }
render() { render() {
const { datasets, loading, error } = this.state; const { datasets, loading, error, filterStr } = this.state;
const { onSelect } = this.props; const { onSelect } = this.props;
return ( return (
<div>
<input class="form-control gcp-input"
type="text"
value={ filterStr }
onChange={ e => this.setState({ filterStr: e.target.value }) }
/>
<DatasetsList <DatasetsList
datasets={datasets} datasets={datasets}
loading={loading} loading={loading}
error={error} error={error}
filter={filterStr}
onSelect={onSelect} onSelect={onSelect}
/> />
</div>
); );
} }
} }
@@ -46,21 +46,23 @@ class DatasetsList extends Component {
} }
render() { render() {
if (this.props.error) { const { loading, datasets, filter, error } = this.props;
return <p>{this.props.error}</p>;
if (error) {
return <p>{error}</p>;
} }
const loadingIcon = ( const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" /> <Icon name="circle-notch" className="loading-icon-spin loading-icon" />
); );
if (this.props.loading) { if (loading) {
return loadingIcon; return loadingIcon;
} }
const body = ( const body = (
<tbody id="DatasetList"> <tbody id="DatasetList">
{this.props.datasets.map(this.renderTableRow)} {datasets.filter(dataset => (dataset.name.split('/')[5].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody> </tbody>
); );
@@ -71,7 +73,7 @@ class DatasetsList extends Component {
<th>{this.props.t('Dataset')}</th> <th>{this.props.t('Dataset')}</th>
</tr> </tr>
</thead> </thead>
{this.props.datasets && body} {datasets && body}
</table> </table>
); );
} }
@@ -46,20 +46,24 @@ class DicomStoreList extends Component {
} }
render() { render() {
if (this.props.error) { const { loading, stores, filter, error } = this.props;
return <p>{this.props.error}</p>;
if (error) {
return <p>{error}</p>;
} }
const loadingIcon = ( const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" /> <Icon name="circle-notch" className="loading-icon-spin loading-icon" />
); );
if (this.props.loading) { if (loading) {
return loadingIcon; return loadingIcon;
} }
const body = ( const body = (
<tbody id="StoreList">{this.props.stores.map(this.renderTableRow)}</tbody> <tbody id="StoreList">{
stores.filter(store => (store.name.split('/')[7].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody>
); );
return ( return (
@@ -69,7 +73,7 @@ class DicomStoreList extends Component {
<th>{this.props.t('DICOM Store')}</th> <th>{this.props.t('DICOM Store')}</th>
</tr> </tr>
</thead> </thead>
{this.props.stores && body} {stores && body}
</table> </table>
); );
} }
@@ -10,6 +10,7 @@ export default class DicomStorePicker extends Component {
loading: true, loading: true,
stores: [], stores: [],
locations: [], locations: [],
filterStr: ""
}; };
static propTypes = { static propTypes = {
@@ -38,16 +39,24 @@ export default class DicomStorePicker extends Component {
} }
render() { render() {
const { stores, loading, error } = this.state; const { stores, loading, error, filterStr } = this.state;
const { onSelect } = this.props; const { onSelect } = this.props;
return ( return (
<DicomStoreList <div>
stores={stores} <input class="form-control gcp-input"
loading={loading} type="text"
error={error} value={ filterStr }
onSelect={onSelect} onChange={ e => this.setState({ filterStr: e.target.value }) }
/> />
<DicomStoreList
stores={stores}
loading={loading}
error={error}
filter={filterStr}
onSelect={onSelect}
/>
</div>
); );
} }
} }
@@ -9,6 +9,7 @@ export default class LocationPicker extends Component {
error: null, error: null,
loading: true, loading: true,
locations: [], locations: [],
filterStr: "",
}; };
static propTypes = { static propTypes = {
@@ -37,15 +38,23 @@ export default class LocationPicker extends Component {
} }
render() { render() {
const { locations, loading, error } = this.state; const { locations, loading, error, filterStr } = this.state;
const { onSelect } = this.props; const { onSelect } = this.props;
return ( return (
<LocationsList <div>
locations={locations} <input class="form-control gcp-input"
loading={loading} type="text"
error={error} value={ filterStr }
onSelect={onSelect} onChange={ e => this.setState({ filterStr: e.target.value }) }
/> />
<LocationsList
locations={locations}
loading={loading}
error={error}
filter={filterStr}
onSelect={onSelect}
/>
</div>
); );
} }
} }
@@ -46,21 +46,23 @@ class LocationsList extends Component {
} }
render() { render() {
if (this.props.error) { const { loading, locations, filter, error } = this.props;
return <p>{this.props.error}</p>;
if (error) {
return <p>{error}</p>;
} }
const loadingIcon = ( const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" /> <Icon name="circle-notch" className="loading-icon-spin loading-icon" />
); );
if (this.props.loading) { if (loading) {
return loadingIcon; return loadingIcon;
} }
const body = ( const body = (
<tbody id="LocationList"> <tbody id="LocationList">
{this.props.locations.map(this.renderTableRow)} {locations.filter(location => (location.name.split('/')[3].toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody> </tbody>
); );
@@ -71,7 +73,7 @@ class LocationsList extends Component {
<th>{this.props.t('Location')}</th> <th>{this.props.t('Location')}</th>
</tr> </tr>
</thead> </thead>
{this.props.locations && body} {locations && body}
</table> </table>
); );
} }
@@ -30,20 +30,29 @@ export default class ProjectPicker extends Component {
this.setState({ this.setState({
projects: response.data.projects || [], projects: response.data.projects || [],
filterStr: "",
loading: false, loading: false,
}); });
} }
render() { render() {
const { projects, loading, error } = this.state; const { projects, loading, filterStr, error } = this.state;
const { onSelect } = this.props; const { onSelect } = this.props;
return ( return (
<ProjectsList <div>
projects={projects} <input class="form-control gcp-input"
loading={loading} type="text"
error={error} value={ filterStr }
onSelect={onSelect} onChange={ e => this.setState({ filterStr: e.target.value }) }
/> />
<ProjectsList
projects={projects}
loading={loading}
filter={filterStr}
error={error}
onSelect={onSelect}
/>
</div>
); );
} }
} }
@@ -50,21 +50,22 @@ class ProjectsList extends Component {
} }
render() { render() {
if (this.props.error) { const { loading, projects, filter, error } = this.props;
return <p>{this.props.error}</p>;
if (error) {
return <p>{error}</p>;
} }
const loadingIcon = ( const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" /> <Icon name="circle-notch" className="loading-icon-spin loading-icon" />
); );
if (this.props.loading) { if (loading) {
return loadingIcon; return loadingIcon;
} }
const body = ( const body = (
<tbody id="ProjectList"> <tbody id="ProjectList">
{this.props.projects.map(this.renderTableRow)} {projects.filter(project => (project.name.toLowerCase().includes(filter.toLowerCase()) || filter=="")).map(this.renderTableRow)}
</tbody> </tbody>
); );
@@ -76,7 +77,7 @@ class ProjectsList extends Component {
<th>{this.props.t('ID')}</th> <th>{this.props.t('ID')}</th>
</tr> </tr>
</thead> </thead>
{this.props.projects && body} {projects && body}
</table> </table>
); );
} }
@@ -141,3 +141,15 @@
margin: 20px auto; margin: 20px auto;
} }
.gcp-input{
height: 40px;
margin: 0 5px 20px 5px;
padding: 0 20px;
cursor: pointer;
border: none;
background-color: var(--input-background-color);
color: var(--input-placeholder-color);
font-size: 10pt;
font-weight: normal;
border-radius: 4px;
}