feat(OHIFStandaloneViewer.js): adding NProgress loading bar on top of the page

This commit is contained in:
biharck 2019-06-06 11:11:26 -03:00 committed by Erik Ziegler
parent 5a3f3e3eff
commit edb0db349b
14 changed files with 271 additions and 52 deletions

View File

@ -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",

View File

@ -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;
}

View File

@ -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 />}
</>
);
}
}

View 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;

View 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;

View File

@ -0,0 +1,4 @@
import Bar from './Bar.js';
import Container from './Container.js';
export { Bar, Container };

View File

@ -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 = () => {

View File

@ -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() {

View File

@ -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>
);
}

View File

@ -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
View 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
View 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>
);
}

View File

@ -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"