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
This commit is contained in:
parent
dc6e9d6bb5
commit
e62f5f8dd2
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
// Top level key
|
|
||||||
viewer: {
|
|
||||||
// First tab
|
// First tab
|
||||||
hotKeysData: {
|
hotkeyDefinitions: [
|
||||||
// hotkeyName, label, keys, column
|
// commandName, label, keys
|
||||||
// zoom: { label: 'Zoom', command: 'Z', column: 0 },
|
// [{ zoom: { label: 'Zoom', keys: ['z'] }}]
|
||||||
},
|
],
|
||||||
// Second tab
|
// Second tab
|
||||||
windowLevelData: {
|
windowLevelData: {
|
||||||
// order, description, window (int), level (int)
|
// order, description, window (int), level (int)
|
||||||
// 0: { description: 'Soft tissue', window: '', level: '' },
|
// 0: { description: 'Soft tissue', window: '', level: '' },
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const preferences = (state, action) => {
|
const preferences = (state, action) => {
|
||||||
|
|||||||
@ -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,6 +95,7 @@ export class UserPreferences extends Component {
|
|||||||
>
|
>
|
||||||
<button>Hotkeys</button>
|
<button>Hotkeys</button>
|
||||||
</li>
|
</li>
|
||||||
|
{false && (
|
||||||
<li
|
<li
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.tabClick(1);
|
this.tabClick(1);
|
||||||
@ -101,6 +104,7 @@ export class UserPreferences extends Component {
|
|||||||
>
|
>
|
||||||
<button>Window Level</button>
|
<button>Window Level</button>
|
||||||
</li>
|
</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;
|
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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,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);
|
||||||
|
|||||||
@ -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,16 @@
|
|||||||
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.hotkeyDefinitions.length > 0
|
||||||
|
? state.preferences.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;
|
||||||
Loading…
Reference in New Issue
Block a user