refactor(GoogleCloud): Clean up some of the Healthcare API Adapter components (#641)

This commit is contained in:
Erik Ziegler 2019-07-10 14:54:27 +02:00 committed by GitHub
parent 4a09f33446
commit 091cab49e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 613 additions and 438 deletions

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import DicomFileUploader from './DicomFileUploader.js';
import DicomFileUploaderModal from './DicomFileUploaderModal.js';
const isActive = a => a.active === true;
@ -17,6 +17,6 @@ const mapStateToProps = state => {
const ConnectedDicomFileUploader = connect(
mapStateToProps,
null
)(DicomFileUploader);
)(DicomFileUploaderModal);
export default ConnectedDicomFileUploader;

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import DicomStorePickerWindow from './DicomStorePickerWindow.js';
import DicomStorePickerModal from './DicomStorePickerModal.js';
const isActive = a => a.active === true;
@ -29,6 +29,6 @@ const mapDispatchToProps = dispatch => {
const ConnectedDicomStorePicker = connect(
mapStateToProps,
mapDispatchToProps
)(DicomStorePickerWindow);
)(DicomStorePickerModal);
export default ConnectedDicomStorePicker;

View File

@ -5,14 +5,11 @@ import DatasetsList from './DatasetsList';
import './googleCloud.css';
export default class DatasetPicker extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
loading: false,
datasets: [],
};
}
state = {
error: null,
loading: true,
datasets: [],
};
static propTypes = {
project: PropTypes.object,
@ -20,7 +17,6 @@ export default class DatasetPicker extends Component {
onSelect: PropTypes.func,
oidcKey: PropTypes.string,
};
static defaultProps = {};
async componentDidMount() {
api.setOidcStorageKey(this.props.oidcKey);
@ -29,12 +25,19 @@ export default class DatasetPicker extends Component {
this.props.project.projectId,
this.props.location.locationId
);
this.loading = false;
if (response.isError) {
this.error = response.message;
this.setState({
error: response.message,
});
return;
}
this.setState({ datasets: response.data.datasets || [] });
this.setState({
datasets: response.data.datasets || [],
loading: false,
});
}
render() {
@ -46,7 +49,7 @@ export default class DatasetPicker extends Component {
loading={loading}
error={error}
onSelect={onSelect}
></DatasetsList>
/>
);
}
}

View File

@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import DicomStorePicker from './DicomStorePicker';
import DatasetPicker from './DatasetPicker';
import ProjectPicker from './ProjectPicker';
@ -7,16 +8,13 @@ import LocationPicker from './LocationPicker';
import GoogleCloudApi from './api/GoogleCloudApi';
import './googleCloud.css';
export default class DatasetSelector extends Component {
constructor(props) {
super(props);
this.state = {
project: null,
location: null,
dataset: null,
unloading: false,
};
}
class DatasetSelector extends Component {
state = {
project: null,
location: null,
dataset: null,
unloading: false,
};
static propTypes = {
id: PropTypes.string,
@ -25,23 +23,22 @@ export default class DatasetSelector extends Component {
canClose: PropTypes.string,
setServers: PropTypes.func.isRequired,
};
static defaultProps = {};
onProjectSelect = project => {
this.setState({
project: project,
project,
});
};
onLocationSelect = location => {
this.setState({
location: location,
location,
});
};
onDatasetSelect = dataset => {
this.setState({
dataset: dataset,
dataset,
});
};
@ -92,65 +89,68 @@ export default class DatasetSelector extends Component {
onDatasetSelect,
onDicomStoreSelect,
} = this;
let projectBreadcrumbs = (
<div className="gcp-picker--path">
<span>{this.props.t('Select a Project')}</span>
</div>
);
if (project) {
projectBreadcrumbs = (
<div className="gcp-picker--path">
<span onClick={onProjectClick}>{project.name}</span>
{project && location && (
<span onClick={onLocationClick}>
{' '}
-> {location.name.split('/')[3]}
</span>
)}
{project && location && dataset && (
<span onClick={onDatasetClick}>
{' '}
-> {dataset.name.split('/')[5]}
</span>
)}
</div>
);
}
return (
<>
<span className="gcp-picker--title">Google Cloud Healthcare API</span>
{project && (
<div className="gcp-picker--path">
<span onClick={onProjectClick}>{project.name}</span>
{project && location && (
<>
<span onClick={onLocationClick}>
{' '}
-> {location.name.split('/')[3]}
</span>
{project && location && dataset && (
<span onClick={onDatasetClick}>
{' '}
-> {dataset.name.split('/')[5]}
</span>
)}
</>
)}
</div>
)}
{projectBreadcrumbs}
{!project && (
<ProjectPicker
oidcKey={this.props.oidcKey}
onSelect={onProjectSelect}
></ProjectPicker>
/>
)}
{project && !location && (
<>
<LocationPicker
oidcKey={this.props.oidcKey}
project={project}
onSelect={onLocationSelect}
></LocationPicker>
</>
<LocationPicker
oidcKey={this.props.oidcKey}
project={project}
onSelect={onLocationSelect}
/>
)}
{project && location && !dataset && (
<>
<DatasetPicker
oidcKey={this.props.oidcKey}
project={project}
location={location}
onSelect={onDatasetSelect}
></DatasetPicker>
</>
<DatasetPicker
oidcKey={this.props.oidcKey}
project={project}
location={location}
onSelect={onDatasetSelect}
/>
)}
{project && location && dataset && (
<>
<DicomStorePicker
oidcKey={this.props.oidcKey}
dataset={dataset}
onSelect={onDicomStoreSelect}
></DicomStorePicker>
</>
<DicomStorePicker
oidcKey={this.props.oidcKey}
dataset={dataset}
onSelect={onDicomStoreSelect}
/>
)}
</>
);
}
}
export default withTranslation('Common')(DatasetSelector);

View File

@ -1,14 +1,13 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './googleCloud.css';
import { withTranslation } from 'react-i18next';
import { Icon } from 'react-viewerbase';
export default class DatasetsList extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
};
}
class DatasetsList extends Component {
state = {
search: '',
};
static propTypes = {
datasets: PropTypes.array,
@ -16,16 +15,19 @@ export default class DatasetsList extends Component {
error: PropTypes.string,
onSelect: PropTypes.func,
};
static defaultProps = {};
renderTableRow(dataset) {
static defaultProps = {
loading: true,
};
renderTableRow = dataset => {
return (
<tr
key={dataset.name}
className={
this.state.highlightedItem === dataset.name
? 'studylistStudy noselect active'
: 'studylistStudy noselect'
? 'noselect active'
: 'noselect'
}
onMouseEnter={() => {
this.onHighlightItem(dataset.name);
@ -37,21 +39,42 @@ export default class DatasetsList extends Component {
<td>{dataset.name.split('/')[5]}</td>
</tr>
);
}
};
onHighlightItem(dataset) {
this.setState({ highlightedItem: dataset });
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
return loadingIcon;
}
const body = (
<tbody id="DatasetList">
{this.props.datasets.map(this.renderTableRow)}
</tbody>
);
return (
<table id="tblDatasetList" className="studyListToolbar table noselect">
<tbody id="DatasetList">
{this.props.datasets.map(dataset => {
return this.renderTableRow(dataset);
})}
</tbody>
<table id="tblDatasetList" className="gcp-table table noselect">
<thead>
<tr>
<th>{this.props.t('Dataset')}</th>
</tr>
</thead>
{this.props.datasets && body}
</table>
);
}
}
export default withTranslation('Common')(DatasetsList);

View File

@ -1,38 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DicomUploader from './DicomUploader';
class DicomFileUploader extends Component {
static propTypes = {
url: PropTypes.string,
oidcStorageKey: PropTypes.string.isRequired,
onClose: PropTypes.func,
};
state = {
uploaded: false,
};
constructor(props) {
super(props);
this.element = React.createRef();
}
render() {
if (this.props.url != null) {
return (
<div className="gcp-window">
<DicomUploader
url={this.props.url}
oidcKey={this.props.oidcStorageKey}
></DicomUploader>
<button className="btn btn-primary" onClick={this.props.onClose}>
close
</button>
</div>
);
}
return <></>;
}
}
export default DicomFileUploader;

View File

@ -0,0 +1,49 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-bootstrap-modal';
import DicomUploader from './DicomUploader';
import { withTranslation } from 'react-i18next';
class DicomFileUploaderModal extends Component {
static propTypes = {
url: PropTypes.string,
oidcStorageKey: PropTypes.string.isRequired,
onClose: PropTypes.func,
};
state = {
uploaded: false,
};
render() {
if (!this.props.url) {
return null;
}
return (
<Modal
show={this.props.isOpen}
onHide={this.props.onClose}
aria-labelledby="ModalHeader"
className="modal fade themed in"
backdrop={false}
size={'md'}
keyboard={true}
>
<Modal.Header closeButton>
<Modal.Title>
{this.props.t('Google Cloud Healthcare API - DICOM Upload')}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<DicomUploader
url={this.props.url}
oidcKey={this.props.oidcStorageKey}
/>
</Modal.Body>
</Modal>
);
}
}
export default withTranslation('Common')(DicomFileUploaderModal);

View File

@ -1,31 +1,33 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './googleCloud.css';
import { withTranslation } from 'react-i18next';
import { Icon } from 'react-viewerbase';
export default class DicomStoreList extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
};
}
class DicomStoreList extends Component {
state = {
search: '',
};
static propTypes = {
stores: PropTypes.array,
loading: PropTypes.bool,
loading: PropTypes.bool.isRequired,
error: PropTypes.string,
onSelect: PropTypes.func,
};
static defaultProps = {};
renderTableRow(store) {
static defaultProps = {
loading: true,
};
renderTableRow = store => {
return (
<tr
key={store.name}
className={
this.state.highlightedItem === store.name
? 'studylistStudy noselect active'
: 'studylistStudy noselect'
? 'noselect active'
: 'noselect'
}
onMouseEnter={() => {
this.onHighlightItem(store.name);
@ -37,21 +39,40 @@ export default class DicomStoreList extends Component {
<td className="project">{store.name.split('/')[7]}</td>
</tr>
);
}
};
onHighlightItem(store) {
this.setState({ highlightedItem: store });
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
return loadingIcon;
}
const body = (
<tbody id="StoreList">{this.props.stores.map(this.renderTableRow)}</tbody>
);
return (
<table id="tblStoreList" className="studyListToolbar table noselect">
<tbody id="StoreList">
{this.props.stores.map(store => {
return this.renderTableRow(store);
})}
</tbody>
<table id="tblStoreList" className="gcp-table table noselect">
<thead>
<tr>
<th>{this.props.t('DICOM Store')}</th>
</tr>
</thead>
{this.props.stores && body}
</table>
);
}
}
export default withTranslation('Common')(DicomStoreList);

View File

@ -5,33 +5,36 @@ import DicomStoreList from './DicomStoreList';
import './googleCloud.css';
export default class DicomStorePicker extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
loading: false,
stores: [],
locations: [],
};
}
state = {
error: null,
loading: true,
stores: [],
locations: [],
};
static propTypes = {
dataset: PropTypes.object,
onSelect: PropTypes.func,
};
static defaultProps = {};
async componentDidMount() {
const { authority, client_id } = window.config.oidc[0];
const oidcStorageKey = `oidc.user:${authority}:${client_id}`;
api.setOidcStorageKey(oidcStorageKey);
const response = await api.loadDicomStores(this.props.dataset.name);
this.loading = false;
if (response.isError) {
this.error = response.message;
this.setState({
error: response.message,
});
return;
}
this.setState({ stores: response.data.dicomStores || [] });
this.setState({
stores: response.data.dicomStores || [],
loading: false,
});
}
render() {
@ -44,7 +47,7 @@ export default class DicomStorePicker extends Component {
loading={loading}
error={error}
onSelect={onSelect}
></DicomStoreList>
/>
);
}
}

View File

@ -0,0 +1,67 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-bootstrap-modal';
import DatasetSelector from './DatasetSelector';
import './googleCloud.css';
import { withTranslation } from 'react-i18next';
class DicomStorePickerModal extends Component {
static propTypes = {
url: PropTypes.string,
oidcStorageKey: PropTypes.string.isRequired,
setServers: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func,
};
static defaultProps = {
isOpen: false,
};
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,
},
];
this.props.setServers(servers);
};
render() {
return (
<Modal
show={this.props.isOpen}
onHide={this.props.onClose}
aria-labelledby="ModalHeader"
className="modal fade themed in"
backdrop={false}
size={'md'}
keyboard={true}
>
<Modal.Header closeButton>
<Modal.Title>
{this.props.t('Google Cloud Healthcare API')}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<DatasetSelector
setServers={this.handleEvent}
oidcKey={this.props.oidcStorageKey}
url={this.props.url}
/>
</Modal.Body>
</Modal>
);
}
}
export default withTranslation('Common')(DicomStorePickerModal);

View File

@ -1,57 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DatasetSelector from './DatasetSelector';
import './googleCloud.css';
class DicomStorePickerWindow extends Component {
static propTypes = {
url: PropTypes.string,
oidcStorageKey: PropTypes.string.isRequired,
setServers: PropTypes.func.isRequired,
onClose: PropTypes.func,
};
constructor(props) {
super(props);
this.element = React.createRef();
this.handleEvent = this.handleEvent.bind(this);
}
componentDidMount() {}
handleEvent(data) {
const servers = [
{
name: data.dicomStore,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
qidoSupportsIncludeField: false,
requestOptions: { requestFromBrowser: true },
type: 'dicomWeb',
qidoRoot: data.qidoRoot,
wadoRoot: data.wadoRoot,
wadoUriRoot: data.wadoUriRoot,
active: true,
},
];
this.props.setServers(servers);
}
render() {
return (
<div className="gcp-window">
<DatasetSelector
setServers={this.handleEvent}
oidcKey={this.props.oidcStorageKey}
url={this.props.url}
></DatasetSelector>
<button className="btn btn-primary" onClick={this.props.onClose}>
close
</button>
</div>
);
}
}
export default DicomStorePickerWindow;

View File

@ -6,25 +6,21 @@ import dicomUploader from './api/DicomUploadService';
import './googleCloud.css';
export default class DicomUploader extends Component {
constructor(props) {
super(props);
this.state = {
status: 'Upload',
isCancelled: false,
errorsCount: 0,
files: null,
uploadedVolume: null,
wholeVolumeStr: null,
isFilesListHidden: true,
timeLeft: null,
uploadedList: null,
totalCount: 0,
successfullyUploadedCount: 0,
lastFile: '',
uploadContext: null, // this is probably not needed, but we use this variable to destinguish between different downloads
};
this.uploadFiles = this.uploadFiles.bind(this);
}
state = {
status: 'Upload',
isCancelled: false,
errorsCount: 0,
files: null,
uploadedVolume: null,
wholeVolumeStr: null,
isFilesListHidden: true,
timeLeft: null,
uploadedList: null,
totalCount: 0,
successfullyUploadedCount: 0,
lastFile: '',
uploadContext: null, // this is probably not needed, but we use this variable to distinguish between different downloads
};
static propTypes = {
id: PropTypes.string,
@ -32,7 +28,6 @@ export default class DicomUploader extends Component {
url: PropTypes.string,
oidcKey: PropTypes.string,
};
static defaultProps = {};
filesLeft() {
return (
@ -66,7 +61,7 @@ export default class DicomUploader extends Component {
);
}
uploadFiles(files) {
uploadFiles = files => {
const filesArray = Array.from(files.target.files);
const filesDict = {};
filesArray.forEach((file, i) => {
@ -107,14 +102,14 @@ export default class DicomUploader extends Component {
uploadCallback,
cancellationToken
);
}
};
uploadCallback(fileId, error) {
const file = this.state.files[fileId];
file.processed = true;
if (!error) {
let uploadedVolume = this.state.uploadedVolume + file.size;
this.setState({ uploadedVolume: uploadedVolume });
this.setState({ uploadedVolume });
} else {
file.error = error;
this.setState({ errorsCount: this.state.errorsCount + 1 });
@ -122,17 +117,13 @@ export default class DicomUploader extends Component {
this.setState({ lastFile: file.name });
let uploadedList = this.state.uploadedList;
uploadedList.push(file);
this.setState({ uploadedList: uploadedList });
this.setState({ uploadedList });
}
renderTableRow(file) {
let error = <></>;
if (file.error != null) {
error = (
<>
<font color="red">{file.error}</font>
</>
);
renderTableRow = file => {
let error = null;
if (file.error !== null) {
error = <p style={{ color: 'red' }}>{file.error}</p>;
}
return (
<tr key={file.id}>
@ -141,10 +132,10 @@ export default class DicomUploader extends Component {
</td>
</tr>
);
}
};
render() {
if (this.state.files == null) {
if (this.state.files === null) {
return (
<div className="gcp-dicom-uploader">
<div className="button">
@ -177,28 +168,21 @@ export default class DicomUploader extends Component {
</div>
</div>
);
} else {
return (
<>
<table
id="tblProjectList"
className="studyListToolbar table noselect"
>
<thead>
<tr>
<th className="gcp-picker--path">
{this.percents()}% {this.filesLeft()}
</th>
</tr>
</thead>
<tbody id="ProjectList">
{this.state.uploadedList.map(file => {
return this.renderTableRow(file);
})}
</tbody>
</table>
</>
);
}
return (
<table id="tblProjectList" className="table noselect">
<thead>
<tr>
<th className="gcp-picker--path">
{this.percents()}% {this.filesLeft()}
</th>
</tr>
</thead>
<tbody id="ProjectList">
{this.state.uploadedList.map(this.renderTableRow)}
</tbody>
</table>
);
}
}

View File

@ -5,14 +5,11 @@ import LocationsList from './LocationsList';
import './googleCloud.css';
export default class LocationPicker extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
loading: false,
locations: [],
};
}
state = {
error: null,
loading: true,
locations: [],
};
static propTypes = {
project: PropTypes.object,
@ -25,15 +22,19 @@ export default class LocationPicker extends Component {
const response = await api.loadLocations(this.props.project.projectId);
this.loading = false;
if (response.isError) {
this.error = response.message;
this.setState({
error: response.message,
});
return;
}
this.setState({ locations: response.data.locations || [] });
}
static defaultProps = {};
this.setState({
locations: response.data.locations || [],
loading: false,
});
}
render() {
const { locations, loading, error } = this.state;
@ -44,7 +45,7 @@ export default class LocationPicker extends Component {
loading={loading}
error={error}
onSelect={onSelect}
></LocationsList>
/>
);
}
}

View File

@ -1,30 +1,33 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './googleCloud.css';
export default class LocationsList extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
};
}
import { withTranslation } from 'react-i18next';
import { Icon } from 'react-viewerbase';
class LocationsList extends Component {
state = {
search: '',
};
static propTypes = {
locations: PropTypes.array,
loading: PropTypes.bool,
loading: PropTypes.bool.isRequired,
error: PropTypes.string,
onSelect: PropTypes.func,
};
static defaultProps = {};
renderTableRow(location) {
static defaultProps = {
loading: true,
};
renderTableRow = location => {
return (
<tr
key={location.locationId}
className={
this.state.highlightedItem === location.locationId
? 'studylistStudy noselect active'
: 'studylistStudy noselect'
? 'noselect active'
: 'noselect'
}
onMouseEnter={() => {
this.onHighlightItem(location.locationId);
@ -36,21 +39,42 @@ export default class LocationsList extends Component {
<td>{location.name.split('/')[3]}</td>
</tr>
);
}
};
onHighlightItem(locationId) {
this.setState({ highlightedItem: locationId });
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
return loadingIcon;
}
const body = (
<tbody id="LocationList">
{this.props.locations.map(this.renderTableRow)}
</tbody>
);
return (
<table id="tblLocationList" className="studyListToolbar table noselect">
<tbody id="LocationList">
{this.props.locations.map(project => {
return this.renderTableRow(project);
})}
</tbody>
<table id="tblLocationList" className="gcp-table table noselect">
<thead>
<tr>
<th>{this.props.t('Location')}</th>
</tr>
</thead>
{this.props.locations && body}
</table>
);
}
}
export default withTranslation('Common')(LocationsList);

View File

@ -5,30 +5,33 @@ import ProjectsList from './ProjectsList';
import './googleCloud.css';
export default class ProjectPicker extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
loading: false,
projects: [],
};
}
state = {
error: null,
loading: true,
projects: [],
};
static propTypes = {
onSelect: PropTypes.func,
oidcKey: PropTypes.string,
};
static defaultProps = {};
async componentDidMount() {
api.setOidcStorageKey(this.props.oidcKey);
const response = await api.loadProjects();
this.loading = false;
if (response.isError) {
this.error = response.message;
this.setState({
error: response.message,
});
return;
}
this.setState({ projects: response.data.projects || [] });
this.setState({
projects: response.data.projects || [],
loading: false,
});
}
render() {
@ -40,7 +43,7 @@ export default class ProjectPicker extends Component {
loading={loading}
error={error}
onSelect={onSelect}
></ProjectsList>
/>
);
}
}

View File

@ -1,32 +1,35 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './googleCloud.css';
import { withTranslation } from 'react-i18next';
import { Icon } from 'react-viewerbase';
export default class ProjectsList extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
highlightedItem: null,
};
}
class ProjectsList extends Component {
state = {
search: '',
highlightedItem: null,
};
static propTypes = {
projects: PropTypes.array,
loading: PropTypes.bool,
loading: PropTypes.bool.isRequired,
error: PropTypes.string,
onSelect: PropTypes.func.isRequired,
t: PropTypes.func,
};
static defaultProps = {};
renderTableRow(project) {
static defaultProps = {
loading: true,
};
renderTableRow = project => {
return (
<tr
key={project.projectId}
className={
this.state.highlightedItem === project.projectId
? 'studylistStudy noselect active'
: 'studylistStudy noselect'
? 'noselect active'
: 'noselect'
}
onMouseEnter={() => {
this.onHighlightItem(project.projectId);
@ -37,23 +40,46 @@ export default class ProjectsList extends Component {
}}
>
<td>{project.name}</td>
<td>{project.projectId}</td>
</tr>
);
}
};
onHighlightItem(project) {
this.setState({ highlightedItem: project });
}
render() {
if (this.props.error) {
return <p>{this.props.error}</p>;
}
const loadingIcon = (
<Icon name="circle-notch" className="loading-icon-spin loading-icon" />
);
if (this.props.loading) {
return loadingIcon;
}
const body = (
<tbody id="ProjectList">
{this.props.projects.map(this.renderTableRow)}
</tbody>
);
return (
<table id="tblProjectList" className="studyListToolbar table noselect">
<tbody id="ProjectList">
{this.props.projects.map(project => {
return this.renderTableRow(project);
})}
</tbody>
<table id="tblProjectList" className="gcp-table table noselect">
<thead>
<tr>
<th>{this.props.t('Project')}</th>
<th>{this.props.t('ID')}</th>
</tr>
</thead>
{this.props.projects && body}
</table>
);
}
}
export default withTranslation('Common')(ProjectsList);

View File

@ -62,8 +62,8 @@ class DicomUploadService {
}
readFile(file) {
const promise = new Promise((resolve, reject) => {
var reader = new FileReader();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve({
name: file.name,
@ -75,7 +75,6 @@ class DicomUploadService {
reader.onerror = error => reject(error);
reader.readAsArrayBuffer(file);
});
return promise;
}
getClient(url) {

View File

@ -27,7 +27,7 @@ class GoogleCloudApi {
}
async doRequest(urlStr, config = {}, params = {}) {
var url = new URL(urlStr);
const url = new URL(urlStr);
let data = null;
url.search = new URLSearchParams(params);
@ -40,7 +40,7 @@ class GoogleCloudApi {
if (data.nextPageToken != null) {
params.pageToken = data.nextPageToken;
let subPage = await this.doRequest(urlStr, config, params);
for (var key in data) {
for (let key in data) {
if (data.hasOwnProperty(key)) {
data[key] = data[key].concat(subPage.data[key]);
}
@ -49,7 +49,7 @@ class GoogleCloudApi {
return {
isError: false,
status: response.status,
data: data,
data,
};
} else {
return {
@ -81,17 +81,17 @@ class GoogleCloudApi {
}
async loadLocations(projectId) {
return this.doRequest(this.urlBaseProject + `/${projectId}/locations`);
return this.doRequest(`${this.urlBaseProject}/${projectId}/locations`);
}
async loadDatasets(projectId, locationId) {
return this.doRequest(
this.urlBaseProject + `/${projectId}/locations/${locationId}/datasets`
`${this.urlBaseProject}/${projectId}/locations/${locationId}/datasets`
);
}
async loadDicomStores(dataset) {
return this.doRequest(this.urlBase + `/${dataset}/dicomStores`);
return this.doRequest(`${this.urlBase}/${dataset}/dicomStores`);
}
}

View File

@ -1,3 +1,76 @@
.loading-icon-spin {
animation: spin 2s linear infinite;
}
.loading-icon {
font-size: 30px;
margin: 20px 0;
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
.gcp-table {
width: 100%;
max-width: 100%;
margin-bottom: 20px;
border-spacing: 0;
border-collapse: collapse;
}
.gcp-table > tr {
height: 20px;
}
.gcp-table > thead {
white-space: nowrap;
}
.gcp-table > thead tr th {
padding: 0;
border-bottom: 1px solid var(--ui-border-color-active);
width: 100%;
text-align: left;
border-top: 0;
}
.gcp-table > tbody tr {
padding: 5px;
background-color: var(--ui-gray-darker);
}
.gcp-table > tbody tr:nth-child(even) {
background-color: var(--ui-gray-dark);
}
.gcp-table > tbody tr td {
padding: 8px;
height: $body-cell-height;
line-height: $body-cell-height;
color: var(--table-text-primary-color);
font-weight: 300;
border-top: 1px solid var(--ui-gray-lighter);
border-bottom: 1px solid var(--ui-gray-lighter);
white-space: nowrap;
}
.gcp-table > tbody tr td.emptyCell {
color: #516873;
}
.gcp-table > tbody tr td.emptyValue {
color: var(--ui-gray-light);
}
.gcp-table > tbody tr:hover,
.gcp-table > tbody tr:active,
.gcp-table > tbody tr.active {
background-color: var(--table-hover-color);
color: var(--text-primary-color);
}
.gcp-table > tbody tr:hover td,
.gcp-table > tbody tr:active td,
.gcp-table > tbody tr.active td {
border-top: 1px solid var(--ui-gray-lighter);
border-bottom: 1px solid var(--ui-gray-lighter);
background-color: var(--table-hover-color);
}
.gcp-files-selector {
display: flex;
justify-content: space-between;
@ -32,10 +105,37 @@
display: none;
}
.gcp-picker {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
width: 650px;
height: 600px;
background-color: #161a1f;
padding: 20px 30px;
position: relative;
}
.gcp-dicom-picker__exit {
cursor: pointer;
position: absolute;
top: 30px;
right: 30px;
width: 14px !important;
height: 14px !important;
filter: opacity(0.5);
}
.gcp-dicom-picker__exit:hover {
filter: opacity(1);
}
.gcp-picker--btn {
margin: auto;
}
.gcp-picker--title {
color: #ffffff;
font-size: 24px;
font-weight: 28px;
text-align: center;
margin: 20px auto;
}
@ -44,6 +144,11 @@
color: #ffffff;
font-size: 16px;
font-weight: 28px;
text-align: leaft;
text-align: left;
margin: 20px auto;
}
.gcp-dicom-uploader .button {
float: left;
margin: 5px;
}

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from 'ohif-core';
import { withRouter } from 'react-router-dom';
import { withTranslation } from 'react-i18next';
import { StudyList } from 'react-viewerbase';
import ConnectedHeader from '../connectedComponents/ConnectedHeader.js';
import moment from 'moment';
@ -14,7 +15,6 @@ class StudyListWithData extends Component {
studies: [],
error: null,
modalComponentId: null,
showStudyList: true,
};
static propTypes = {
@ -47,7 +47,6 @@ class StudyListWithData extends Component {
if (!this.props.server && window.config.enableGoogleCloudAdapter) {
this.setState({
modalComponentId: 'DicomStorePicker',
showStudyList: false,
});
} else {
this.searchForStudies({
@ -64,7 +63,6 @@ class StudyListWithData extends Component {
if (this.props.server !== prevProps.server) {
this.setState({
modalComponentId: null,
showStudyList: true,
searchData: null,
studies: null,
});
@ -151,7 +149,6 @@ class StudyListWithData extends Component {
openModal = modalComponentId => {
this.setState({
modalComponentId,
showStudyList: false,
});
};
@ -167,10 +164,9 @@ class StudyListWithData extends Component {
this.searchForStudies(searchData);
};
update = () => {
closeModals = () => {
this.setState({
modalComponentId: null,
showStudyList: true,
});
};
@ -181,49 +177,47 @@ class StudyListWithData extends Component {
return <div>Loading...</div>;
}
let healthCareApiButtons = '';
let healthCareApiWindows = '';
let healthCareApiButtons = null;
let healthCareApiWindows = null;
// TODO: This should probably be a prop
if (window.config.enableGoogleCloudAdapter) {
if (this.state.modalComponentId) {
if (this.state.modalComponentId === 'DicomStorePicker') {
healthCareApiWindows = (
<ConnectedDicomStorePicker onClose={this.update} />
);
} else if (this.state.modalComponentId === 'DicomFilesUploader') {
healthCareApiWindows = (
<ConnectedDicomFilesUploader onClose={this.update} />
);
}
healthCareApiWindows = (
<>
<div className="col-md-4 col-md-offset-2 layoutChooser pagination-area">
{healthCareApiWindows}
</div>
</>
);
}
healthCareApiButtons = (
healthCareApiWindows = (
<>
<div className="form-inline form-group pull-right">
<button
className="btn btn-primary "
onClick={() => this.openModal('DicomStorePicker')}
>
Change Dicom Store
</button>
<button
className="btn btn-primary"
onClick={() => this.openModal('DicomFilesUploader')}
>
Upload Studies
</button>
</div>
<ConnectedDicomStorePicker
isOpen={this.state.modalComponentId === 'DicomStorePicker'}
onClose={this.closeModals}
/>
<ConnectedDicomFilesUploader
isOpen={this.state.modalComponentId === 'DicomFilesUploader'}
onClose={this.closeModals}
/>
</>
);
healthCareApiButtons = (
<div
className="form-inline btn-group pull-right"
style={{ padding: '20px' }}
>
<button
className="btn btn-primary"
onClick={() => this.openModal('DicomStorePicker')}
>
{this.props.t('Change DICOM Store')}
</button>
<button
className="btn btn-primary"
onClick={() => this.openModal('DicomFilesUploader')}
>
{this.props.t('Upload Studies')}
</button>
</div>
);
}
let studyList = <></>;
studyList = (
<div name="paginationArea">
const studyList = (
<div className="paginationArea">
<StudyList
studies={this.state.studies}
studyListFunctionsEnabled={false}
@ -250,4 +244,4 @@ class StudyListWithData extends Component {
}
}
export default withRouter(StudyListWithData);
export default withRouter(withTranslation('Common')(StudyListWithData));

View File

@ -4313,7 +4313,7 @@ debug@3.2.6, debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
dependencies:
ms "^2.1.1"
debuglog@*, debuglog@^1.0.1:
debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
@ -6958,7 +6958,7 @@ import-local@^2.0.0:
pkg-dir "^3.0.0"
resolve-cwd "^2.0.0"
imurmurhash@*, imurmurhash@^0.1.4:
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
@ -8602,7 +8602,7 @@ libnpm@^2.0.1:
read-package-json "^2.0.13"
stringify-package "^1.0.0"
libnpmaccess@*, libnpmaccess@^3.0.1:
libnpmaccess@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-3.0.1.tgz#5b3a9de621f293d425191aa2e779102f84167fa8"
integrity sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA==
@ -8631,7 +8631,7 @@ libnpmhook@^5.0.2:
get-stream "^4.0.0"
npm-registry-fetch "^3.8.0"
libnpmorg@*, libnpmorg@^1.0.0:
libnpmorg@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-1.0.0.tgz#979b868c48ba28c5820e3bb9d9e73c883c16a232"
integrity sha512-o+4eVJBoDGMgRwh2lJY0a8pRV2c/tQM/SxlqXezjcAg26Qe9jigYVs+Xk0vvlYDWCDhP0g74J8UwWeAgsB7gGw==
@ -8656,7 +8656,7 @@ libnpmpublish@^1.1.0:
semver "^5.5.1"
ssri "^6.0.1"
libnpmsearch@*, libnpmsearch@^2.0.0:
libnpmsearch@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-2.0.1.tgz#eccc73a8fbf267d765d18082b85daa2512501f96"
integrity sha512-K0yXyut9MHHCAH+DOiglQCpmBKPZXSUu76+BE2maSEfQN15OwNaA/Aiioe9lRFlVFOr7WcuJCY+VSl+gLi9NTA==
@ -8665,7 +8665,7 @@ libnpmsearch@*, libnpmsearch@^2.0.0:
get-stream "^4.0.0"
npm-registry-fetch "^3.8.0"
libnpmteam@*, libnpmteam@^1.0.1:
libnpmteam@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-1.0.1.tgz#ff704b1b6c06ea674b3b1101ac3e305f5114f213"
integrity sha512-gDdrflKFCX7TNwOMX1snWojCoDE5LoRWcfOC0C/fqF7mBq8Uz9zWAX4B2RllYETNO7pBupBaSyBDkTAC15cAMg==
@ -8923,11 +8923,6 @@ lodash-es@^4.17.11:
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0"
integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==
lodash._baseindexof@*:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=
lodash._baseuniq@~4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
@ -8936,33 +8931,11 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0"
lodash._root "~3.0.0"
lodash._bindcallback@*:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4=
lodash._cacheindexof@*:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=
lodash._createcache@*:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=
dependencies:
lodash._getnative "^3.0.0"
lodash._createset@~4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
lodash._getnative@*, lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
lodash._reinterpolate@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@ -9038,11 +9011,6 @@ lodash.once@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash.restparam@*:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
lodash.set@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
@ -10125,7 +10093,7 @@ npm-pick-manifest@^2.2.3:
npm-package-arg "^6.0.0"
semver "^5.4.1"
npm-profile@*, npm-profile@^4.0.1:
npm-profile@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-4.0.1.tgz#d350f7a5e6b60691c7168fbb8392c3603583f5aa"
integrity sha512-NQ1I/1Q7YRtHZXkcuU1/IyHeLy6pd+ScKg4+DQHdfsm769TGq6HPrkbuNJVJS4zwE+0mvvmeULzQdWn2L2EsVA==