From 2bc361cdca2b18bcb03c0eb0a7cd7ef43f83e3c8 Mon Sep 17 00:00:00 2001 From: ladeirarodolfo <39910206+ladeirarodolfo@users.noreply.github.com> Date: Fri, 11 Oct 2019 04:59:38 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Improve=20usability=20of?= =?UTF-8?q?=20Google=20Cloud=20adapter,=20including=20direct=20routes=20to?= =?UTF-8?q?=20studies=20(#989)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platform/core/src/redux/index.js | 2 + platform/core/src/redux/index.test.js | 2 +- platform/core/src/redux/localStorage.js | 6 +- platform/core/src/redux/sessionStorage.js | 28 +++++ platform/core/src/utils/index.js | 3 + platform/core/src/utils/index.test.js | 1 + platform/core/src/utils/urlUtil.js | 67 ++++++++++ platform/viewer/src/OHIFStandaloneViewer.js | 76 ++---------- platform/viewer/src/customHooks/useServer.js | 77 ++++++++++++ .../src/googleCloud/DicomStorePickerModal.js | 17 +-- .../src/googleCloud/api/GoogleCloudApi.js | 7 ++ .../src/googleCloud/utils/getServers.js | 32 +++++ .../src/routes/IHEInvokeImageDisplay.js | 21 +--- platform/viewer/src/routes/ViewerRouting.js | 55 ++++++--- platform/viewer/src/routes/routesUtil.js | 114 ++++++++++++++++++ platform/viewer/src/store/index.js | 12 +- .../viewer/src/studylist/StudyListRouting.js | 23 +--- .../viewer/src/studylist/StudyListWithData.js | 8 +- 18 files changed, 413 insertions(+), 138 deletions(-) create mode 100644 platform/core/src/redux/sessionStorage.js create mode 100644 platform/core/src/utils/urlUtil.js create mode 100644 platform/viewer/src/customHooks/useServer.js create mode 100644 platform/viewer/src/googleCloud/utils/getServers.js create mode 100644 platform/viewer/src/routes/routesUtil.js diff --git a/platform/core/src/redux/index.js b/platform/core/src/redux/index.js index 26c1988d6..aa41ca20e 100644 --- a/platform/core/src/redux/index.js +++ b/platform/core/src/redux/index.js @@ -1,11 +1,13 @@ import actions from './actions.js'; import reducers from './reducers'; import localStorage from './localStorage.js'; +import sessionStorage from './sessionStorage.js'; const redux = { reducers, actions, localStorage, + sessionStorage, }; export default redux; diff --git a/platform/core/src/redux/index.test.js b/platform/core/src/redux/index.test.js index cd1c82cf2..748adfcaa 100644 --- a/platform/core/src/redux/index.test.js +++ b/platform/core/src/redux/index.test.js @@ -2,7 +2,7 @@ import redux from './index.js'; describe('redux exports', () => { test('have not changed', () => { - const expectedExports = ['actions', 'reducers', 'localStorage'].sort(); + const expectedExports = ['actions', 'reducers', 'localStorage', 'sessionStorage'].sort(); const exports = Object.keys(redux).sort(); diff --git a/platform/core/src/redux/localStorage.js b/platform/core/src/redux/localStorage.js index 89ab0d0b1..96a2505c1 100644 --- a/platform/core/src/redux/localStorage.js +++ b/platform/core/src/redux/localStorage.js @@ -1,6 +1,8 @@ +const LocalStorageApi = window.localStorage; +const localStorageKey = 'state'; export const loadState = () => { try { - const serializedState = window.localStorage.getItem('state'); + const serializedState = LocalStorageApi.getItem(localStorageKey); if (!serializedState) { return undefined; } @@ -14,7 +16,7 @@ export const loadState = () => { export const saveState = state => { try { const serializedState = JSON.stringify(state); - localStorage.setItem('state', serializedState); + LocalStorageApi.setItem(localStorageKey, serializedState); } catch (e) {} }; diff --git a/platform/core/src/redux/sessionStorage.js b/platform/core/src/redux/sessionStorage.js new file mode 100644 index 000000000..49360eafc --- /dev/null +++ b/platform/core/src/redux/sessionStorage.js @@ -0,0 +1,28 @@ +const SessionStorageApi = window.sessionStorage; +const sessionStorageKey = 'state'; +export const loadState = () => { + try { + const serializedState = SessionStorageApi.getItem(sessionStorageKey); + if (!serializedState) { + return undefined; + } + + return JSON.parse(serializedState); + } catch (e) { + return undefined; + } +}; + +export const saveState = state => { + try { + const serializedState = JSON.stringify(state); + SessionStorageApi.setItem(sessionStorageKey, serializedState); + } catch (e) {} +}; + +const sessionStorage = { + saveState, + loadState, +}; + +export default sessionStorage; diff --git a/platform/core/src/utils/index.js b/platform/core/src/utils/index.js index 0b57b2e47..1f3ff98a5 100644 --- a/platform/core/src/utils/index.js +++ b/platform/core/src/utils/index.js @@ -9,6 +9,7 @@ import studyMetadataManager from './studyMetadataManager'; import updateMetaDataManager from './updateMetaDataManager.js'; import writeScript from './writeScript.js'; import DicomLoaderService from './dicomLoaderService.js'; +import * as urlUtil from './urlUtil'; const utils = { guid, @@ -23,6 +24,7 @@ const utils = { updateMetaDataManager, DICOMTagDescriptions, DicomLoaderService, + urlUtil, }; export { @@ -38,6 +40,7 @@ export { updateMetaDataManager, DICOMTagDescriptions, DicomLoaderService, + urlUtil, }; export default utils; diff --git a/platform/core/src/utils/index.test.js b/platform/core/src/utils/index.test.js index a2d127a52..a80bcf130 100644 --- a/platform/core/src/utils/index.test.js +++ b/platform/core/src/utils/index.test.js @@ -15,6 +15,7 @@ describe('Top level exports', () => { 'updateMetaDataManager', 'DICOMTagDescriptions', 'DicomLoaderService', + 'urlUtil' ].sort(); const exports = Object.keys(utils.default).sort(); diff --git a/platform/core/src/utils/urlUtil.js b/platform/core/src/utils/urlUtil.js new file mode 100644 index 000000000..83bb52dd0 --- /dev/null +++ b/platform/core/src/utils/urlUtil.js @@ -0,0 +1,67 @@ +import lib from 'query-string'; + +const PARAM_SEPARATOR = ';'; +const PARAM_PATTERN_IDENTIFIER = ':'; + +function toLowerCaseFirstLetter(word) { + return word[0].toLowerCase() + word.slice(1); +} +const getFilters = (location = {}) => { + const { search } = location; + + if (!search) { + return; + } + + const searchParameters = parse(search); + const filters = {}; + + Object.entries(searchParameters).forEach(([key, value]) => { + filters[toLowerCaseFirstLetter(key)] = value; + }); + + return filters; +}; + +const decode = (strToDecode = '') => { + try { + const decoded = window.atob(strToDecode); + return decoded; + } catch (e) { + return strToDecode; + } +}; + +const parse = toParse => { + if (toParse) { + return lib.parse(toParse); + } + + return {}; +}; +const parseParam = paramStr => { + const _paramDecoded = decode(paramStr); + if (_paramDecoded && typeof _paramDecoded === 'string') { + return _paramDecoded.split(PARAM_SEPARATOR); + } +}; + +const replaceParam = (path = '', paramKey, paramValue) => { + const paramPattern = `${PARAM_PATTERN_IDENTIFIER}${paramKey}`; + if (paramValue) { + return path.replace(paramPattern, paramValue); + } + + return path; +}; + +const queryString = { + getQueryFilters: getFilters, +}; + +const paramString = { + parseParam: parseParam, + replaceParam: replaceParam, +}; + +export { parse, queryString, paramString }; diff --git a/platform/viewer/src/OHIFStandaloneViewer.js b/platform/viewer/src/OHIFStandaloneViewer.js index c7674253f..d5e3cf8f1 100644 --- a/platform/viewer/src/OHIFStandaloneViewer.js +++ b/platform/viewer/src/OHIFStandaloneViewer.js @@ -8,44 +8,18 @@ import { connect } from 'react-redux'; import { ViewerbaseDragDropContext } from '@ohif/ui'; import { SignoutCallbackComponent } from 'redux-oidc'; import asyncComponent from './components/AsyncComponent.js'; +import * as RoutesUtil from './routes/routesUtil'; + import NotFound from './routes/NotFound.js'; import { Bar, Container } from './components/LoadingBar/'; import './OHIFStandaloneViewer.css'; import './variables.css'; import './theme-tide.css'; - // Contexts import AppContext from './context/AppContext'; - -// Dynamic Import Routes (CodeSplitting) -const IHEInvokeImageDisplay = asyncComponent(() => - import( - /* webpackChunkName: "IHEInvokeImageDisplay" */ './routes/IHEInvokeImageDisplay.js' - ) -); -const ViewerRouting = asyncComponent(() => - import(/* webpackChunkName: "ViewerRouting" */ './routes/ViewerRouting.js') -); -const StudyListRouting = asyncComponent(() => - import( - /* webpackChunkName: "StudyListRouting" */ './studylist/StudyListRouting.js' - ) -); -const StandaloneRouting = asyncComponent(() => - import( - /* webpackChunkName: "StandaloneRouting" */ './routes/StandaloneRouting.js' - ) -); const CallbackPage = asyncComponent(() => import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js') ); -const ViewerLocalFileData = asyncComponent(() => - import( - /* webpackChunkName: "ViewerLocalFileData" */ './connectedComponents/ViewerLocalFileData.js' - ) -); - -const reload = () => window.location.reload(); class OHIFStandaloneViewer extends Component { static contextType = AppContext; @@ -86,7 +60,11 @@ class OHIFStandaloneViewer extends Component { return ( - + @@ -184,8 +128,8 @@ class OHIFStandaloneViewer extends Component { )} - - + + {!noMatchingRoutes && routes.map(({ path, Component }) => ( diff --git a/platform/viewer/src/customHooks/useServer.js b/platform/viewer/src/customHooks/useServer.js new file mode 100644 index 000000000..5d2efb8f3 --- /dev/null +++ b/platform/viewer/src/customHooks/useServer.js @@ -0,0 +1,77 @@ +import React, { useContext } from 'react'; +import GoogleCloudApi from '../googleCloud/api/GoogleCloudApi'; + +import * as GoogleCloudUtilServers from '../googleCloud/utils/getServers'; +import { useSelector, useDispatch } from 'react-redux'; + +// Contexts +import AppContext from '../context/AppContext'; + +const getActiveServer = servers => { + const isActive = a => a.active === true; + + return servers && servers.servers && servers.servers.find(isActive); +}; + +const getServers = (appConfig, project, location, dataset, dicomStore) => { + let servers = []; + if (appConfig.enableGoogleCloudAdapter) { + const pathUrl = GoogleCloudApi.getUrlBaseDicomWeb( + project, + location, + dataset, + dicomStore + ); + const data = { + project, + location, + dataset, + dicomStore, + wadoUriRoot: pathUrl, + qidoRoot: pathUrl, + wadoRoot: pathUrl, + }; + servers = GoogleCloudUtilServers.getServers(data, dicomStore); + } + + return servers; +}; + +const updateServer = ( + appConfig, + dispatch, + project, + location, + dataset, + dicomStore +) => { + const servers = getServers(appConfig, project, location, dataset, dicomStore); + + if (servers && servers.length) { + const action = { + type: 'SET_SERVERS', + servers, + }; + dispatch(action); + } +}; + +export default function useServer({ + project, + location, + dataset, + dicomStore, +} = {}) { + // Hooks + const servers = useSelector(state => state && state.servers); + const dispatch = useDispatch(); + const { appConfig = {} } = useContext(AppContext); + + const server = getActiveServer(servers); + + if (!server) { + updateServer(appConfig, dispatch, project, location, dataset, dicomStore); + } else { + return server; + } +} diff --git a/platform/viewer/src/googleCloud/DicomStorePickerModal.js b/platform/viewer/src/googleCloud/DicomStorePickerModal.js index b6a4b310b..023a5c842 100644 --- a/platform/viewer/src/googleCloud/DicomStorePickerModal.js +++ b/platform/viewer/src/googleCloud/DicomStorePickerModal.js @@ -4,6 +4,7 @@ import Modal from 'react-bootstrap-modal'; import DatasetSelector from './DatasetSelector'; import './googleCloud.css'; import { withTranslation } from 'react-i18next'; +import * as GoogleCloudUtilServers from './utils/getServers'; class DicomStorePickerModal extends Component { static propTypes = { @@ -19,21 +20,7 @@ class DicomStorePickerModal extends Component { }; handleEvent = data => { - const servers = [ - { - name: data.dicomStore, - imageRendering: 'wadors', - thumbnailRendering: 'wadors', - qidoSupportsIncludeField: false, - type: 'dicomWeb', - qidoRoot: data.qidoRoot, - wadoRoot: data.wadoRoot, - wadoUriRoot: data.wadoUriRoot, - active: true, - supportsFuzzyMatching: false, - }, - ]; - + const servers = GoogleCloudUtilServers.getServers(data, data.dicomstore); this.props.setServers(servers); }; diff --git a/platform/viewer/src/googleCloud/api/GoogleCloudApi.js b/platform/viewer/src/googleCloud/api/GoogleCloudApi.js index 08db55d5f..2bd527fee 100644 --- a/platform/viewer/src/googleCloud/api/GoogleCloudApi.js +++ b/platform/viewer/src/googleCloud/api/GoogleCloudApi.js @@ -22,6 +22,13 @@ class GoogleCloudApi { return this.urlBase + `/projects`; } + getUrlBaseDicomWeb(project, location, dataset, dicomStore) { + return ( + this.urlBase + + `/projects/${project}/locations/${location}/datasets/${dataset}/dicomStores/${dicomStore}/dicomWeb` + ); + } + async doRequest(urlStr, config = {}, params = {}) { const url = new URL(urlStr); let data = null; diff --git a/platform/viewer/src/googleCloud/utils/getServers.js b/platform/viewer/src/googleCloud/utils/getServers.js new file mode 100644 index 000000000..bab857d39 --- /dev/null +++ b/platform/viewer/src/googleCloud/utils/getServers.js @@ -0,0 +1,32 @@ +const getServers = (data, name) => { + const { + wadoUriRoot, + qidoRoot, + wadoRoot, + dataset = '', + dicomStore = '', + location = '', + project = '', + } = data; + + return [ + { + name: name, + dataset, + dicomStore, + location, + project, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + type: 'dicomWeb', + active: true, + wadoUriRoot, + qidoRoot, + wadoRoot, + supportsFuzzyMatching: false, + qidoSupportsIncludeField: false, + }, + ]; +}; + +export { getServers }; diff --git a/platform/viewer/src/routes/IHEInvokeImageDisplay.js b/platform/viewer/src/routes/IHEInvokeImageDisplay.js index bd1c13d80..8b4e92f5c 100644 --- a/platform/viewer/src/routes/IHEInvokeImageDisplay.js +++ b/platform/viewer/src/routes/IHEInvokeImageDisplay.js @@ -1,29 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router-dom'; -import queryString from 'query-string'; import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData.js'; - -function decodeStudyUids(studyUids) { - const decodedData = window.atob(studyUids); - - return decodedData.split(';'); -} - -function getQueryParameters(location) { - if (location) { - return queryString.parse(location.search); - } - - return {}; -} +import OHIF from '@ohif/core'; +const { urlUtil: UrlUtil } = OHIF.utils; function IHEInvokeImageDisplay({ location }) { const { // patientID, requestType, studyUID, - } = getQueryParameters(location); + } = UrlUtil.parse(location.search); switch (requestType) { case 'STUDY': @@ -36,7 +23,7 @@ function IHEInvokeImageDisplay({ location }) { case 'STUDYBASE64': return ( ); diff --git a/platform/viewer/src/routes/ViewerRouting.js b/platform/viewer/src/routes/ViewerRouting.js index f8166d6b4..b26cd99d2 100644 --- a/platform/viewer/src/routes/ViewerRouting.js +++ b/platform/viewer/src/routes/ViewerRouting.js @@ -1,26 +1,47 @@ import React from 'react'; import PropTypes from 'prop-types'; import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData'; +import useServer from '../customHooks/useServer'; +import OHIF from '@ohif/core'; +const { urlUtil: UrlUtil } = OHIF.utils; -function ViewerRouting({ match }) { - const { studyInstanceUids, seriesInstanceUids } = match.params; +/** + * Get array of seriesUIDs from param or from queryString + * @param {*} seriesInstanceUIDs + * @param {*} location + */ +const getSeriesInstanceUIDs = (seriesInstanceUIDs, routeLocation) => { + const queryFilters = UrlUtil.queryString.getQueryFilters(routeLocation); + const querySeriesUIDs = queryFilters && queryFilters['SeriesInstanceUID']; + const _seriesInstanceUIDs = seriesInstanceUIDs || querySeriesUIDs; - let studyUIDs; - let seriesUIDs; + return UrlUtil.paramString.parseParam(_seriesInstanceUIDs); +}; - if (studyInstanceUids && !seriesInstanceUids) { - studyUIDs = studyInstanceUids.split(';'); - } else if (studyInstanceUids && seriesInstanceUids) { - studyUIDs = [studyInstanceUids]; - seriesUIDs = match.params.seriesInstanceUids.split(';'); +function ViewerRouting({ match: routeMatch, location: routeLocation }) { + const { + project, + location, + dataset, + dicomStore, + studyInstanceUids, + seriesInstanceUids, + } = routeMatch.params; + const server = useServer({ project, location, dataset, dicomStore }); + + const studyUIDs = UrlUtil.paramString.parseParam(studyInstanceUids); + const seriesUIDs = getSeriesInstanceUIDs(seriesInstanceUids, routeLocation); + + if (server && studyUIDs) { + return ( + + ); } - return ( - - ); + return null; } ViewerRouting.propTypes = { @@ -28,6 +49,10 @@ ViewerRouting.propTypes = { params: PropTypes.shape({ studyInstanceUids: PropTypes.string.isRequired, seriesInstanceUids: PropTypes.string, + dataset: PropTypes.string, + dicomStore: PropTypes.string, + location: PropTypes.string, + project: PropTypes.string, }), }), }; diff --git a/platform/viewer/src/routes/routesUtil.js b/platform/viewer/src/routes/routesUtil.js new file mode 100644 index 000000000..fc8aa43c8 --- /dev/null +++ b/platform/viewer/src/routes/routesUtil.js @@ -0,0 +1,114 @@ +import asyncComponent from '../components/AsyncComponent.js'; + +import OHIF from '@ohif/core'; +const { urlUtil: UrlUtil } = OHIF.utils; + +// Dynamic Import Routes (CodeSplitting) +const IHEInvokeImageDisplay = asyncComponent(() => + import( + /* webpackChunkName: "IHEInvokeImageDisplay" */ './IHEInvokeImageDisplay.js' + ) +); +const ViewerRouting = asyncComponent(() => + import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js') +); + +const StudyListRouting = asyncComponent(() => + import( + /* webpackChunkName: "StudyListRouting" */ '../studylist/StudyListRouting.js' + ) +); +const StandaloneRouting = asyncComponent(() => + import(/* webpackChunkName: "StandaloneRouting" */ './StandaloneRouting.js') +); +const ViewerLocalFileData = asyncComponent(() => + import( + /* webpackChunkName: "ViewerLocalFileData" */ '../connectedComponents/ViewerLocalFileData.js' + ) +); + +const reload = () => window.location.reload(); + +const ROUTES_DEF = { + default: { + viewer: { + path: '/viewer/:studyInstanceUids', + component: ViewerRouting, + }, + standaloneViewer: { + path: '/viewer', + component: StandaloneRouting, + }, + list: { + path: ['/studylist', '/'], + component: StudyListRouting, + condition: appConfig => { + return appConfig.showStudyList !== undefined + ? appConfig.showStudyList + : true; + }, + }, + local: { + path: '/local', + component: ViewerLocalFileData, + }, + IHEInvokeImageDisplay: { + path: '/IHEInvokeImageDisplay', + }, + }, + gcloud: { + viewer: { + path: + '/projects/:project/locations/:location/datasets/:dataset/dicomStores/:dicomStore/study/:studyInstanceUids', + component: ViewerRouting, + condition: appConfig => { + return !!appConfig.enableGoogleCloudAdapter; + }, + }, + }, +}; + +const getRoutes = appConfig => { + const routes = []; + for (let keyConfig in ROUTES_DEF) { + const routesConfig = ROUTES_DEF[keyConfig]; + + for (let routeKey in routesConfig) { + const route = routesConfig[routeKey]; + const validRoute = + typeof route.condition === 'function' + ? route.condition(appConfig) + : true; + + if (validRoute) { + routes.push({ + path: route.path, + Component: route.component, + }); + } + } + } + + return routes; +}; + +const parseViewerPath = (appConfig = {}, server = {}, params) => { + let viewerPath = ROUTES_DEF.default.viewer.path; + if (appConfig.enableGoogleCloudAdapter) { + viewerPath = ROUTES_DEF.gcloud.viewer.path; + } + + const _paramsCopy = Object.assign({}, server, params); + + for (let key in _paramsCopy) { + viewerPath = UrlUtil.paramString.replaceParam( + viewerPath, + key, + _paramsCopy[key] + ); + } + + return viewerPath; +}; + +export { getRoutes, parseViewerPath, reload }; diff --git a/platform/viewer/src/store/index.js b/platform/viewer/src/store/index.js index 1167184d2..53fb0e07c 100644 --- a/platform/viewer/src/store/index.js +++ b/platform/viewer/src/store/index.js @@ -13,7 +13,7 @@ import thunkMiddleware from 'redux-thunk'; // Combine our @ohif/core, ui, and oidc reducers // Set init data, using values found in localStorage -const { reducers, localStorage } = redux; +const { reducers, localStorage, sessionStorage } = redux; const middleware = [thunkMiddleware]; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; @@ -21,9 +21,14 @@ reducers.ui = layoutReducers; reducers.oidc = oidcReducer; const rootReducer = combineReducers(reducers); +const preloadedState = { + ...localStorage.loadState(), + ...sessionStorage.loadState(), +}; + const store = createStore( rootReducer, - localStorage.loadState(), // preloadedState + preloadedState, composeEnhancers(applyMiddleware(...middleware)) ); @@ -33,6 +38,9 @@ store.subscribe(() => { localStorage.saveState({ preferences: store.getState().preferences, }); + sessionStorage.saveState({ + servers: store.getState().servers, + }); }); export default store; diff --git a/platform/viewer/src/studylist/StudyListRouting.js b/platform/viewer/src/studylist/StudyListRouting.js index 8023df157..3cb72c0ad 100644 --- a/platform/viewer/src/studylist/StudyListRouting.js +++ b/platform/viewer/src/studylist/StudyListRouting.js @@ -1,33 +1,18 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router-dom'; -import queryString from 'query-string'; import ConnectedStudyList from './ConnectedStudyList'; +import OHIF from '@ohif/core'; +const { urlUtil: UrlUtil } = OHIF.utils; + // Contexts import AppContext from '../context/AppContext'; -// TODO: Move to @ohif/ui - -function toLowerCaseFirstLetter(word) { - return word[0].toLowerCase() + word.slice(1); -} - -function getFilters({ search }) { - const searchParameters = queryString.parse(search); - const filters = {}; - - Object.entries(searchParameters).forEach(([key, value]) => { - filters[toLowerCaseFirstLetter(key)] = value; - }); - - return filters; -} - function StudyListRouting({ location }) { const { appConfig = {} } = useContext(AppContext); - const filters = location ? getFilters(location) : undefined; + const filters = UrlUtil.queryString.getQueryFilters(location); let studyListFunctionsEnabled = false; if (appConfig.studyListFunctionsEnabled) { diff --git a/platform/viewer/src/studylist/StudyListWithData.js b/platform/viewer/src/studylist/StudyListWithData.js index 656cf64f0..e7bf60681 100644 --- a/platform/viewer/src/studylist/StudyListWithData.js +++ b/platform/viewer/src/studylist/StudyListWithData.js @@ -6,6 +6,7 @@ import { withRouter } from 'react-router-dom'; import { withTranslation } from 'react-i18next'; import { StudyList } from '@ohif/ui'; import ConnectedHeader from '../connectedComponents/ConnectedHeader.js'; +import * as RoutesUtil from '../routes/routesUtil'; import moment from 'moment'; import ConnectedDicomFilesUploader from '../googleCloud/ConnectedDicomFilesUploader'; import ConnectedDicomStorePicker from '../googleCloud/ConnectedDicomStorePicker'; @@ -175,7 +176,12 @@ class StudyListWithData extends Component { }; onSelectItem = studyInstanceUID => { - this.props.history.push(`/viewer/${studyInstanceUID}`); + const { appConfig = {} } = this.context; + const { server } = this.props; + const viewerPath = RoutesUtil.parseViewerPath(appConfig, server, { + studyInstanceUids: studyInstanceUID, + }); + this.props.history.push(viewerPath); }; onSearch = searchData => {