From 94f7cfb08e3490488394efc42ef089ebe55e86be Mon Sep 17 00:00:00 2001 From: Alireza Date: Wed, 6 Sep 2023 14:09:26 -0400 Subject: [PATCH] fix(hotkeys): preserve hotkeys if changed, and reduce re-rendering (#3635) --- .../default/src/ViewerLayout/ViewerHeader.tsx | 118 ++++++++++++++++ extensions/default/src/ViewerLayout/index.tsx | 131 ++---------------- platform/app/src/routes/Mode/Mode.tsx | 2 +- platform/app/src/routes/WorkList/WorkList.tsx | 4 +- 4 files changed, 132 insertions(+), 123 deletions(-) create mode 100644 extensions/default/src/ViewerLayout/ViewerHeader.tsx diff --git a/extensions/default/src/ViewerLayout/ViewerHeader.tsx b/extensions/default/src/ViewerLayout/ViewerHeader.tsx new file mode 100644 index 000000000..6b7f5bfd3 --- /dev/null +++ b/extensions/default/src/ViewerLayout/ViewerHeader.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router'; + +import { ErrorBoundary, UserPreferences, AboutModal, Header, useModal } from '@ohif/ui'; +import i18n from '@ohif/i18n'; +import { hotkeys } from '@ohif/core'; +import { useAppConfig } from '@state'; +import Toolbar from '../Toolbar/Toolbar'; + +const { availableLanguages, defaultLanguage, currentLanguage } = i18n; + +function ViewerHeader({ hotkeysManager, extensionManager, servicesManager }) { + const [appConfig] = useAppConfig(); + const navigate = useNavigate(); + const location = useLocation(); + + const onClickReturnButton = () => { + const { pathname } = location; + const dataSourceIdx = pathname.indexOf('/', 1); + const query = new URLSearchParams(window.location.search); + const configUrl = query.get('configUrl'); + + const dataSourceName = pathname.substring(dataSourceIdx + 1); + const existingDataSource = extensionManager.getDataSources(dataSourceName); + + const searchQuery = new URLSearchParams(); + if (dataSourceIdx !== -1 && existingDataSource) { + searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1)); + } + + if (configUrl) { + searchQuery.append('configUrl', configUrl); + } + + navigate({ + pathname: '/', + search: decodeURIComponent(searchQuery.toString()), + }); + }; + + const { t } = useTranslation(); + const { show, hide } = useModal(); + const { hotkeyDefinitions, hotkeyDefaults } = hotkeysManager; + const versionNumber = process.env.VERSION_NUMBER; + const commitHash = process.env.COMMIT_HASH; + + const menuOptions = [ + { + title: t('Header:About'), + icon: 'info', + onClick: () => + show({ + content: AboutModal, + title: 'About OHIF Viewer', + contentProps: { versionNumber, commitHash }, + }), + }, + { + title: t('Header:Preferences'), + icon: 'settings', + onClick: () => + show({ + title: t('UserPreferencesModal:User Preferences'), + content: UserPreferences, + contentProps: { + hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(hotkeyDefaults), + hotkeyDefinitions, + currentLanguage: currentLanguage(), + availableLanguages, + defaultLanguage, + onCancel: () => { + hotkeys.stopRecord(); + hotkeys.unpause(); + hide(); + }, + onSubmit: ({ hotkeyDefinitions, language }) => { + if (language.value !== currentLanguage().value) { + i18n.changeLanguage(language.value); + } + hotkeysManager.setHotkeys(hotkeyDefinitions); + hide(); + }, + onReset: () => hotkeysManager.restoreDefaultBindings(), + hotkeysModule: hotkeys, + }, + }), + }, + ]; + + if (appConfig.oidc) { + menuOptions.push({ + title: t('Header:Logout'), + icon: 'power-off', + onClick: async () => { + navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`); + }, + }); + } + + return ( +
+ +
+ +
+
+
+ ); +} + +export default ViewerHeader; diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx index 7feb08be4..d7e4335ca 100644 --- a/extensions/default/src/ViewerLayout/index.tsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -1,24 +1,10 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { useNavigate } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import { useLocation } from 'react-router'; -import { - SidePanel, - ErrorBoundary, - UserPreferences, - AboutModal, - Header, - useModal, - LoadingIndicatorProgress, -} from '@ohif/ui'; -import i18n from '@ohif/i18n'; -import { ServicesManager, HangingProtocolService, hotkeys, CommandsManager } from '@ohif/core'; +import { SidePanel, ErrorBoundary, LoadingIndicatorProgress } from '@ohif/ui'; +import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core'; import { useAppConfig } from '@state'; -import Toolbar from '../Toolbar/Toolbar'; - -const { availableLanguages, defaultLanguage, currentLanguage } = i18n; +import ViewerHeader from './ViewerHeader'; function ViewerLayout({ // From Extension Module Params @@ -35,100 +21,9 @@ function ViewerLayout({ rightPanelDefaultClosed = false, }): React.FunctionComponent { const [appConfig] = useAppConfig(); - const navigate = useNavigate(); - const location = useLocation(); - - const onClickReturnButton = () => { - const { pathname } = location; - const dataSourceIdx = pathname.indexOf('/', 1); - // const search = - // dataSourceIdx === -1 - // ? undefined - // : `datasources=${pathname.substring(dataSourceIdx + 1)}`; - - // Todo: Handle parameters in a better way. - const query = new URLSearchParams(window.location.search); - const configUrl = query.get('configUrl'); - - const dataSourceName = pathname.substring(dataSourceIdx + 1); - const existingDataSource = extensionManager.getDataSources(dataSourceName); - - const searchQuery = new URLSearchParams(); - if (dataSourceIdx !== -1 && existingDataSource) { - searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1)); - } - - if (configUrl) { - searchQuery.append('configUrl', configUrl); - } - - navigate({ - pathname: '/', - search: decodeURIComponent(searchQuery.toString()), - }); - }; - - const { t } = useTranslation(); - const { show, hide } = useModal(); - - const [showLoadingIndicator, setShowLoadingIndicator] = useState(appConfig.showLoadingIndicator); const { hangingProtocolService } = servicesManager.services; - - const { hotkeyDefinitions, hotkeyDefaults } = hotkeysManager; - const versionNumber = process.env.VERSION_NUMBER; - const commitHash = process.env.COMMIT_HASH; - - const menuOptions = [ - { - title: t('Header:About'), - icon: 'info', - onClick: () => - show({ - content: AboutModal, - title: 'About OHIF Viewer', - contentProps: { versionNumber, commitHash }, - }), - }, - { - title: t('Header:Preferences'), - icon: 'settings', - onClick: () => - show({ - title: t('UserPreferencesModal:User Preferences'), - content: UserPreferences, - contentProps: { - hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(hotkeyDefaults), - hotkeyDefinitions, - currentLanguage: currentLanguage(), - availableLanguages, - defaultLanguage, - onCancel: () => { - hotkeys.stopRecord(); - hotkeys.unpause(); - hide(); - }, - onSubmit: ({ hotkeyDefinitions, language }) => { - i18n.changeLanguage(language.value); - hotkeysManager.setHotkeys(hotkeyDefinitions); - hide(); - }, - onReset: () => hotkeysManager.restoreDefaultBindings(), - hotkeysModule: hotkeys, - }, - }), - }, - ]; - - if (appConfig.oidc) { - menuOptions.push({ - title: t('Header:Logout'), - icon: 'power-off', - onClick: async () => { - navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`); - }, - }); - } + const [showLoadingIndicator, setShowLoadingIndicator] = useState(appConfig.showLoadingIndicator); /** * Set body classes (tailwindcss) that don't allow vertical @@ -210,18 +105,11 @@ function ViewerLayout({ return (
-
- -
- -
-
-
+
{ - i18n.changeLanguage(state.language.value); + if (state.language.value !== currentLanguage().value) { + i18n.changeLanguage(state.language.value); + } hotkeysManager.setHotkeys(state.hotkeyDefinitions); hide(); },