feat(OHIFStandaloneViewer.js): adding NProgress loading bar on top of the page
This commit is contained in:
parent
5a3f3e3eff
commit
edb0db349b
@ -82,6 +82,7 @@
|
|||||||
"@ohif/extension-dicom-pdf": "0.0.7",
|
"@ohif/extension-dicom-pdf": "0.0.7",
|
||||||
"@ohif/extension-vtk": "0.1.0",
|
"@ohif/extension-vtk": "0.1.0",
|
||||||
"@ohif/i18n": "0.1.0",
|
"@ohif/i18n": "0.1.0",
|
||||||
|
"@tanem/react-nprogress": "^1.1.25",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"cornerstone-core": "^2.2.8",
|
"cornerstone-core": "^2.2.8",
|
||||||
"cornerstone-math": "^0.1.8",
|
"cornerstone-math": "^0.1.8",
|
||||||
@ -103,6 +104,7 @@
|
|||||||
"react-resize-detector": "^4.2.0",
|
"react-resize-detector": "^4.2.0",
|
||||||
"react-router": "^5.0.1",
|
"react-router": "^5.0.1",
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
|
"react-transition-group": "^4.1.1",
|
||||||
"react-viewerbase": "0.12.0",
|
"react-viewerbase": "0.12.0",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
|
|||||||
@ -5,13 +5,28 @@ body,
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
font-family: Roboto,OpenSans,HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;
|
font-family: Roboto, OpenSans, HelveticaNeue-Light, Helvetica Neue Light,
|
||||||
|
Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root .fade-enter {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root .fade-enter-active {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root .fade-exit {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root .fade-exit-active {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 200ms;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { withRouter, matchPath } from 'react-router';
|
||||||
|
import { Route, Switch } from 'react-router-dom';
|
||||||
|
import { NProgress } from '@tanem/react-nprogress';
|
||||||
|
import { CSSTransition } from 'react-transition-group';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { ViewerbaseDragDropContext } from 'react-viewerbase';
|
||||||
// import asyncComponent from './components/AsyncComponent.js'
|
// import asyncComponent from './components/AsyncComponent.js'
|
||||||
import IHEInvokeImageDisplay from './routes/IHEInvokeImageDisplay.js';
|
import IHEInvokeImageDisplay from './routes/IHEInvokeImageDisplay.js';
|
||||||
import ViewerRouting from './routes/ViewerRouting.js';
|
import ViewerRouting from './routes/ViewerRouting.js';
|
||||||
import StudyListRouting from './studylist/StudyListRouting.js';
|
import StudyListRouting from './studylist/StudyListRouting.js';
|
||||||
import StandaloneRouting from './routes/StandaloneRouting.js';
|
import StandaloneRouting from './routes/StandaloneRouting.js';
|
||||||
import CallbackPage from './CallbackPage.js';
|
import CallbackPage from './routes/CallbackPage.js';
|
||||||
import { withRouter } from 'react-router';
|
import NotFound from './routes/NotFound.js';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Bar, Container } from './components/LoadingBar/';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { ViewerbaseDragDropContext } from 'react-viewerbase';
|
|
||||||
|
|
||||||
import './OHIFStandaloneViewer.css';
|
import './OHIFStandaloneViewer.css';
|
||||||
import './variables.css';
|
import './variables.css';
|
||||||
import './theme-tide.css';
|
import './theme-tide.css';
|
||||||
@ -32,6 +35,10 @@ import './theme-tide.css';
|
|||||||
const reload = () => window.location.reload();
|
const reload = () => window.location.reload();
|
||||||
|
|
||||||
class OHIFStandaloneViewer extends Component {
|
class OHIFStandaloneViewer extends Component {
|
||||||
|
state = {
|
||||||
|
isLoading: false,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
user: PropTypes.object,
|
user: PropTypes.object,
|
||||||
@ -79,24 +86,84 @@ class OHIFStandaloneViewer extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: this approach for routing is caused by the conflict between
|
||||||
|
* react-transition-group and react-router's <Switch> component.
|
||||||
|
*
|
||||||
|
* See http://reactcommunity.org/react-transition-group/with-react-router/
|
||||||
|
*/
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
Component: StudyListRouting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/studylist',
|
||||||
|
Component: StudyListRouting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/viewer',
|
||||||
|
Component: StandaloneRouting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/viewer/:studyInstanceUids',
|
||||||
|
Component: ViewerRouting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/study/:studyInstanceUid/series/:seriesInstanceUids',
|
||||||
|
Component: ViewerRouting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/IHEInvokeImageDisplay',
|
||||||
|
Component: IHEInvokeImageDisplay,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentPath = this.props.location.pathname;
|
||||||
|
const noMatchingRoutes = !routes.find(r =>
|
||||||
|
matchPath(currentPath, {
|
||||||
|
path: r.path,
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<>
|
||||||
|
<NProgress isAnimating={this.state.isLoading}>
|
||||||
|
{({ isFinished, progress, animationDuration }) => (
|
||||||
|
<Container
|
||||||
|
isFinished={isFinished}
|
||||||
|
animationDuration={animationDuration}
|
||||||
|
>
|
||||||
|
<Bar progress={progress} animationDuration={animationDuration} />
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</NProgress>
|
||||||
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
<Route exact path="/silent-refresh.html" onEnter={reload} />
|
||||||
<Route exact path="/logout-redirect.html" onEnter={reload} />
|
<Route exact path="/logout-redirect.html" onEnter={reload} />
|
||||||
<Route exact path="/studylist" component={StudyListRouting} />
|
{!noMatchingRoutes &&
|
||||||
<Route exact path="/" component={StudyListRouting} />
|
routes.map(({ path, Component }) => (
|
||||||
<Route exact path="/viewer" component={StandaloneRouting} />
|
<Route key={path} exact path={path}>
|
||||||
<Route path="/viewer/:studyInstanceUids" component={ViewerRouting} />
|
{({ match }) => (
|
||||||
<Route
|
<CSSTransition
|
||||||
path="/study/:studyInstanceUid/series/:seriesInstanceUids"
|
in={match !== null}
|
||||||
component={ViewerRouting}
|
timeout={300}
|
||||||
/>
|
classNames="fade"
|
||||||
<Route
|
unmountOnExit
|
||||||
path="/IHEInvokeImageDisplay"
|
onEnter={() => {
|
||||||
component={IHEInvokeImageDisplay}
|
this.setState({ isLoading: true });
|
||||||
/>
|
}}
|
||||||
<Route render={() => <div> Sorry, this page does not exist. </div>} />
|
onEntered={() => {
|
||||||
</Switch>
|
this.setState({ isLoading: false });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{match === null ? <></> : <Component match={match} />}
|
||||||
|
</CSSTransition>
|
||||||
|
)}
|
||||||
|
</Route>
|
||||||
|
))}
|
||||||
|
{noMatchingRoutes && <NotFound />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/components/LoadingBar/Bar.js
Normal file
32
src/components/LoadingBar/Bar.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Bar = ({ progress, animationDuration }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: '#29d',
|
||||||
|
height: 2,
|
||||||
|
left: 0,
|
||||||
|
marginLeft: `${(-1 + progress) * 100}%`,
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
transition: `margin-left ${animationDuration}ms linear`,
|
||||||
|
width: '100%',
|
||||||
|
zIndex: 1031,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
boxShadow: '0 0 10px #29d, 0 0 5px #29d',
|
||||||
|
display: 'block',
|
||||||
|
height: '100%',
|
||||||
|
opacity: 1,
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
transform: 'rotate(3deg) translate(0px, -4px)',
|
||||||
|
width: 100,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Bar;
|
||||||
15
src/components/LoadingBar/Container.js
Normal file
15
src/components/LoadingBar/Container.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Container = ({ children, isFinished, animationDuration }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
opacity: isFinished ? 0 : 1,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
transition: `opacity ${animationDuration}ms linear`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Container;
|
||||||
4
src/components/LoadingBar/index.js
Normal file
4
src/components/LoadingBar/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import Bar from './Bar.js';
|
||||||
|
import Container from './Container.js';
|
||||||
|
|
||||||
|
export { Bar, Container };
|
||||||
@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
class FlexboxLayout extends Component {
|
class FlexboxLayout extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
studies: PropTypes.array.isRequired,
|
studies: PropTypes.array,
|
||||||
leftSidebarOpen: PropTypes.bool.isRequired,
|
leftSidebarOpen: PropTypes.bool.isRequired,
|
||||||
rightSidebarOpen: PropTypes.bool.isRequired,
|
rightSidebarOpen: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
@ -19,12 +19,24 @@ class FlexboxLayout extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
if (this.props.studies) {
|
||||||
const studiesForBrowser = this.getStudiesForBrowser();
|
const studiesForBrowser = this.getStudiesForBrowser();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
studiesForBrowser,
|
studiesForBrowser,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.studies !== prevProps.studies) {
|
||||||
|
const studiesForBrowser = this.getStudiesForBrowser();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
studiesForBrowser,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getStudiesForBrowser = () => {
|
getStudiesForBrowser = () => {
|
||||||
const { studies } = this.props;
|
const { studies } = this.props;
|
||||||
|
|||||||
@ -165,10 +165,26 @@ class Viewer extends Component {
|
|||||||
onMeasurementsUpdated: this.onMeasurementsUpdated,
|
onMeasurementsUpdated: this.onMeasurementsUpdated,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.currentTimepointId = currentTimepointId;
|
||||||
|
this.timepointApi = timepointApi;
|
||||||
|
this.measurementApi = measurementApi;
|
||||||
|
|
||||||
|
if (studies) {
|
||||||
const patientId = studies[0] && studies[0].patientId;
|
const patientId = studies[0] && studies[0].patientId;
|
||||||
timepointApi.retrieveTimepoints({ patientId });
|
timepointApi.retrieveTimepoints({ patientId });
|
||||||
measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
|
measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.studies !== prevProps.studies) {
|
||||||
|
const { studies } = this.props;
|
||||||
|
const patientId = studies[0] && studies[0].patientId;
|
||||||
|
const currentTimepointId = this.currentTimepointId;
|
||||||
|
this.timepointApi.retrieveTimepoints({ patientId });
|
||||||
|
this.measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import React from 'react';
|
|||||||
class ViewerMain extends Component {
|
class ViewerMain extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
activeViewportIndex: PropTypes.number.isRequired,
|
activeViewportIndex: PropTypes.number.isRequired,
|
||||||
studies: PropTypes.array.isRequired,
|
studies: PropTypes.array,
|
||||||
viewportSpecificData: PropTypes.object.isRequired,
|
viewportSpecificData: PropTypes.object.isRequired,
|
||||||
layout: PropTypes.object.isRequired,
|
layout: PropTypes.object.isRequired,
|
||||||
setViewportSpecificData: PropTypes.func.isRequired,
|
setViewportSpecificData: PropTypes.func.isRequired,
|
||||||
@ -59,12 +59,24 @@ class ViewerMain extends Component {
|
|||||||
//window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
|
//window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||||
|
|
||||||
// Get all the display sets for the viewer studies
|
// Get all the display sets for the viewer studies
|
||||||
|
if (this.props.studies) {
|
||||||
const displaySets = this.getDisplaySets(this.props.studies);
|
const displaySets = this.getDisplaySets(this.props.studies);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
displaySets,
|
displaySets,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.studies !== prevProps.studies) {
|
||||||
|
const displaySets = this.getDisplaySets(this.props.studies);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
displaySets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getViewportData = () => {
|
getViewportData = () => {
|
||||||
const viewportData = [];
|
const viewportData = [];
|
||||||
@ -125,6 +137,7 @@ class ViewerMain extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="ViewerMain">
|
<div className="ViewerMain">
|
||||||
|
{this.state.displaySets.length && (
|
||||||
<ConnectedLayoutManager
|
<ConnectedLayoutManager
|
||||||
studies={this.props.studies}
|
studies={this.props.studies}
|
||||||
viewportData={this.getViewportData()}
|
viewportData={this.getViewportData()}
|
||||||
@ -133,6 +146,7 @@ class ViewerMain extends Component {
|
|||||||
{/* Children to add to each viewport that support children */}
|
{/* Children to add to each viewport that support children */}
|
||||||
<ConnectedToolContextMenu />
|
<ConnectedToolContextMenu />
|
||||||
</ConnectedLayoutManager>
|
</ConnectedLayoutManager>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,8 +75,6 @@ class ViewerRetrieveStudyData extends Component {
|
|||||||
render() {
|
render() {
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
return <div>Error: {JSON.stringify(this.state.error)}</div>;
|
return <div>Error: {JSON.stringify(this.state.error)}</div>;
|
||||||
} else if (!this.state.studies) {
|
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
9
src/routes/NotFound.css
Normal file
9
src/routes/NotFound.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.not-found {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
16
src/routes/NotFound.js
Normal file
16
src/routes/NotFound.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './NotFound.css';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<div className={'not-found'}>
|
||||||
|
<div>
|
||||||
|
<h4>Sorry, this page does not exist.</h4>
|
||||||
|
<h5>
|
||||||
|
<Link to={'/'}>Go back to the Study List</Link>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
yarn.lock
19
yarn.lock
@ -1472,6 +1472,15 @@
|
|||||||
"@svgr/plugin-svgo" "^4.0.3"
|
"@svgr/plugin-svgo" "^4.0.3"
|
||||||
loader-utils "^1.1.0"
|
loader-utils "^1.1.0"
|
||||||
|
|
||||||
|
"@tanem/react-nprogress@^1.1.25":
|
||||||
|
version "1.1.25"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanem/react-nprogress/-/react-nprogress-1.1.25.tgz#1c8e75a30181d5f9da3f99f8c97414d25bd7d398"
|
||||||
|
integrity sha512-SpHAd1ln3KeJf1UWIioPKXclzZ82UrmIH0piGY0J3llx30OAROEML6EWbIpHledMg7qAuB4NpJbHG0uPldBJmg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.4.5"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
"@types/babel__core@^7.1.0":
|
"@types/babel__core@^7.1.0":
|
||||||
version "7.1.2"
|
version "7.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f"
|
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f"
|
||||||
@ -12509,6 +12518,16 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0:
|
|||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
react-lifecycles-compat "^3.0.4"
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
|
react-transition-group@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.1.1.tgz#16efe9ac8c68306f6bef59c7da5a96b4dfd9fb32"
|
||||||
|
integrity sha512-K/N1wqJ2GRP2yj3WBqEUYa0KV5fiaAWpUfU9SpHOHefeKvyrO+VrnMBML21M19QZoVbDZKmuQFHZYoMMi1xuJA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.4.5"
|
||||||
|
dom-helpers "^3.4.0"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
react-viewerbase@0.12.0:
|
react-viewerbase@0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.12.0.tgz#0315c4360ec51619e36defce403cebd29c430485"
|
resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.12.0.tgz#0315c4360ec51619e36defce403cebd29c430485"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user