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-vtk": "0.1.0",
|
||||
"@ohif/i18n": "0.1.0",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-math": "^0.1.8",
|
||||
@ -103,6 +104,7 @@
|
||||
"react-resize-detector": "^4.2.0",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"react-transition-group": "^4.1.1",
|
||||
"react-viewerbase": "0.12.0",
|
||||
"redux": "^4.0.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
|
||||
@ -1,17 +1,32 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: Roboto, OpenSans, HelveticaNeue-Light, Helvetica Neue Light,
|
||||
Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
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 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 IHEInvokeImageDisplay from './routes/IHEInvokeImageDisplay.js';
|
||||
import ViewerRouting from './routes/ViewerRouting.js';
|
||||
import StudyListRouting from './studylist/StudyListRouting.js';
|
||||
import StandaloneRouting from './routes/StandaloneRouting.js';
|
||||
import CallbackPage from './CallbackPage.js';
|
||||
import { withRouter } from 'react-router';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { ViewerbaseDragDropContext } from 'react-viewerbase';
|
||||
|
||||
import CallbackPage from './routes/CallbackPage.js';
|
||||
import NotFound from './routes/NotFound.js';
|
||||
import { Bar, Container } from './components/LoadingBar/';
|
||||
import './OHIFStandaloneViewer.css';
|
||||
import './variables.css';
|
||||
import './theme-tide.css';
|
||||
@ -32,6 +35,10 @@ import './theme-tide.css';
|
||||
const reload = () => window.location.reload();
|
||||
|
||||
class OHIFStandaloneViewer extends Component {
|
||||
state = {
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
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 (
|
||||
<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="/logout-redirect.html" onEnter={reload} />
|
||||
<Route exact path="/studylist" component={StudyListRouting} />
|
||||
<Route exact path="/" component={StudyListRouting} />
|
||||
<Route exact path="/viewer" component={StandaloneRouting} />
|
||||
<Route path="/viewer/:studyInstanceUids" component={ViewerRouting} />
|
||||
<Route
|
||||
path="/study/:studyInstanceUid/series/:seriesInstanceUids"
|
||||
component={ViewerRouting}
|
||||
/>
|
||||
<Route
|
||||
path="/IHEInvokeImageDisplay"
|
||||
component={IHEInvokeImageDisplay}
|
||||
/>
|
||||
<Route render={() => <div> Sorry, this page does not exist. </div>} />
|
||||
</Switch>
|
||||
{!noMatchingRoutes &&
|
||||
routes.map(({ path, Component }) => (
|
||||
<Route key={path} exact path={path}>
|
||||
{({ match }) => (
|
||||
<CSSTransition
|
||||
in={match !== null}
|
||||
timeout={300}
|
||||
classNames="fade"
|
||||
unmountOnExit
|
||||
onEnter={() => {
|
||||
this.setState({ isLoading: true });
|
||||
}}
|
||||
onEntered={() => {
|
||||
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 {
|
||||
static propTypes = {
|
||||
studies: PropTypes.array.isRequired,
|
||||
studies: PropTypes.array,
|
||||
leftSidebarOpen: PropTypes.bool.isRequired,
|
||||
rightSidebarOpen: PropTypes.bool.isRequired,
|
||||
};
|
||||
@ -19,11 +19,23 @@ class FlexboxLayout extends Component {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const studiesForBrowser = this.getStudiesForBrowser();
|
||||
if (this.props.studies) {
|
||||
const studiesForBrowser = this.getStudiesForBrowser();
|
||||
|
||||
this.setState({
|
||||
studiesForBrowser,
|
||||
});
|
||||
this.setState({
|
||||
studiesForBrowser,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.studies !== prevProps.studies) {
|
||||
const studiesForBrowser = this.getStudiesForBrowser();
|
||||
|
||||
this.setState({
|
||||
studiesForBrowser,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStudiesForBrowser = () => {
|
||||
|
||||
@ -165,9 +165,25 @@ class Viewer extends Component {
|
||||
onMeasurementsUpdated: this.onMeasurementsUpdated,
|
||||
});
|
||||
|
||||
const patientId = studies[0] && studies[0].patientId;
|
||||
timepointApi.retrieveTimepoints({ patientId });
|
||||
measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
|
||||
this.currentTimepointId = currentTimepointId;
|
||||
this.timepointApi = timepointApi;
|
||||
this.measurementApi = measurementApi;
|
||||
|
||||
if (studies) {
|
||||
const patientId = studies[0] && studies[0].patientId;
|
||||
timepointApi.retrieveTimepoints({ patientId });
|
||||
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() {
|
||||
|
||||
@ -9,7 +9,7 @@ import React from 'react';
|
||||
class ViewerMain extends Component {
|
||||
static propTypes = {
|
||||
activeViewportIndex: PropTypes.number.isRequired,
|
||||
studies: PropTypes.array.isRequired,
|
||||
studies: PropTypes.array,
|
||||
viewportSpecificData: PropTypes.object.isRequired,
|
||||
layout: PropTypes.object.isRequired,
|
||||
setViewportSpecificData: PropTypes.func.isRequired,
|
||||
@ -59,11 +59,23 @@ class ViewerMain extends Component {
|
||||
//window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||
|
||||
// Get all the display sets for the viewer studies
|
||||
const displaySets = this.getDisplaySets(this.props.studies);
|
||||
if (this.props.studies) {
|
||||
const displaySets = this.getDisplaySets(this.props.studies);
|
||||
|
||||
this.setState({
|
||||
displaySets,
|
||||
});
|
||||
this.setState({
|
||||
displaySets,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.studies !== prevProps.studies) {
|
||||
const displaySets = this.getDisplaySets(this.props.studies);
|
||||
|
||||
this.setState({
|
||||
displaySets,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getViewportData = () => {
|
||||
@ -125,14 +137,16 @@ class ViewerMain extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="ViewerMain">
|
||||
<ConnectedLayoutManager
|
||||
{this.state.displaySets.length && (
|
||||
<ConnectedLayoutManager
|
||||
studies={this.props.studies}
|
||||
viewportData={this.getViewportData()}
|
||||
setViewportData={this.setViewportData}
|
||||
>
|
||||
{/* Children to add to each viewport that support children */}
|
||||
<ConnectedToolContextMenu />
|
||||
</ConnectedLayoutManager>
|
||||
>
|
||||
{/* Children to add to each viewport that support children */}
|
||||
<ConnectedToolContextMenu />
|
||||
</ConnectedLayoutManager>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -75,8 +75,6 @@ class ViewerRetrieveStudyData extends Component {
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return <div>Error: {JSON.stringify(this.state.error)}</div>;
|
||||
} else if (!this.state.studies) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
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"
|
||||
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":
|
||||
version "7.1.2"
|
||||
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"
|
||||
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:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-viewerbase/-/react-viewerbase-0.12.0.tgz#0315c4360ec51619e36defce403cebd29c430485"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user