Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new dicom store selection workflow #265

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ interface AppProps {

interface AppState {
clients: { [sopClassUID: string]: DicomWebManager }
defaultClients: { [sopClassUID: string]: DicomWebManager }
user?: User
isLoading: boolean
redirectTo?: string
Expand Down Expand Up @@ -249,13 +250,16 @@ class App extends React.Component<AppProps, AppState> {
message.config({ duration: 5 })
this.addGcpSecondaryAnnotationServer(props.config)

const defaultClients = _createClientMapping({
baseUri,
gcpBaseUrl: props.config.gcpBaseUrl ?? 'https://healthcare.googleapis.com/v1',
settings: props.config.servers,
onError: this.handleDICOMwebError
})

this.state = {
clients: _createClientMapping({
baseUri,
gcpBaseUrl: props.config.gcpBaseUrl ?? 'https://healthcare.googleapis.com/v1',
settings: props.config.servers,
onError: this.handleDICOMwebError
}),
clients: defaultClients,
defaultClients,
isLoading: true,
wasAuthSuccessful: false
}
Expand Down Expand Up @@ -290,6 +294,11 @@ class App extends React.Component<AppProps, AppState> {

handleServerSelection ({ url }: { url: string }): void {
console.info('select DICOMweb server: ', url)
if (url === '' || window.localStorage.getItem('slim_server_selection_mode') === 'default') {
this.setState({ clients: this.state.defaultClients })
return
}
window.localStorage.setItem('slim_selected_server', url)
const tmpClient = new DicomWebManager({
baseUri: '',
settings: [{
Expand Down Expand Up @@ -334,11 +343,11 @@ class App extends React.Component<AppProps, AppState> {
}
const storedPath = window.localStorage.getItem('slim_path')
const storedSearch = window.localStorage.getItem('slim_search')
if (storedPath != null) {
if (storedPath !== null && storedPath !== '') {
const currentPath = window.location.pathname
if (storedPath !== currentPath) {
let path = storedPath
if (storedSearch != null) {
if (storedSearch !== null && storedSearch !== '') {
path += storedSearch
}
window.location.href = path
Expand Down Expand Up @@ -384,10 +393,17 @@ class App extends React.Component<AppProps, AppState> {

componentDidMount (): void {
const path = window.localStorage.getItem('slim_path')
if (path == null) {
if (path === null || path === '') {
window.localStorage.setItem('slim_path', window.location.pathname)
window.localStorage.setItem('slim_search', window.location.search)
}

// Restore cached server selection if it exists
const cachedServerUrl = window.localStorage.getItem('slim_selected_server')
if (cachedServerUrl !== null && cachedServerUrl !== '') {
this.handleServerSelection({ url: cachedServerUrl })
}

this.signIn()
}

Expand Down Expand Up @@ -453,6 +469,7 @@ class App extends React.Component<AppProps, AppState> {
onServerSelection={this.handleServerSelection}
showServerSelectionButton={false}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
<Layout.Content style={layoutContentStyle}>
<FaSpinner />
Expand Down Expand Up @@ -483,6 +500,8 @@ class App extends React.Component<AppProps, AppState> {
onServerSelection={this.handleServerSelection}
onUserLogout={isLogoutPossible ? onLogout : undefined}
showServerSelectionButton={enableServerSelection}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
<Layout.Content style={layoutContentStyle}>
{worklist}
Expand All @@ -501,6 +520,8 @@ class App extends React.Component<AppProps, AppState> {
onServerSelection={this.handleServerSelection}
onUserLogout={isLogoutPossible ? onLogout : undefined}
showServerSelectionButton={enableServerSelection}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
<Layout.Content style={layoutContentStyle}>
<ParametrizedCaseViewer
Expand All @@ -524,6 +545,8 @@ class App extends React.Component<AppProps, AppState> {
onServerSelection={this.handleServerSelection}
onUserLogout={isLogoutPossible ? onLogout : undefined}
showServerSelectionButton={enableServerSelection}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
<Layout.Content style={layoutContentStyle}>
<ParametrizedCaseViewer
Expand All @@ -547,6 +570,8 @@ class App extends React.Component<AppProps, AppState> {
onServerSelection={this.handleServerSelection}
onUserLogout={isLogoutPossible ? onLogout : undefined}
showServerSelectionButton={enableServerSelection}
clients={this.state.clients}
defaultClients={this.state.defaultClients}
/>
Logged out
</Layout>
Expand Down
198 changes: 134 additions & 64 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
Row,
Space,
Badge,
Collapse
Collapse,
Radio,
Tooltip
} from 'antd'
import {
ApiOutlined,
Expand Down Expand Up @@ -44,7 +46,8 @@ interface HeaderProps extends RouteComponentProps {
name: string
email: string
}
clients: { [key: string]: DicomWebManager }
clients?: { [key: string]: DicomWebManager }
defaultClients?: { [key: string]: DicomWebManager }
showWorklistButton: boolean
onServerSelection: ({ url }: { url: string }) => void
onUserLogout?: () => void
Expand All @@ -62,6 +65,7 @@ interface HeaderState {
errorObj: ExtendedCustomError[]
errorCategory: string[]
warnings: string[]
serverSelectionMode: 'default' | 'custom'
}

/**
Expand All @@ -70,12 +74,17 @@ interface HeaderState {
class Header extends React.Component<HeaderProps, HeaderState> {
constructor (props: HeaderProps) {
super(props)
const cachedServerUrl = window.localStorage.getItem('slim_selected_server')
const cachedMode = window.localStorage.getItem('slim_server_selection_mode') as 'default' | 'custom' | null

this.state = {
selectedServerUrl: cachedServerUrl ?? undefined,
isServerSelectionModalVisible: false,
isServerSelectionDisabled: true,
isServerSelectionDisabled: !this.isValidServerUrl(cachedServerUrl),
errorObj: [],
errorCategory: [],
warnings: []
warnings: [],
serverSelectionMode: cachedMode ?? (cachedServerUrl != null && cachedServerUrl !== '' ? 'custom' : 'default')
}

const onErrorHandler = ({ source, error }: {
Expand Down Expand Up @@ -119,6 +128,18 @@ class Header extends React.Component<HeaderProps, HeaderState> {
}
}

isValidServerUrl = (url: string | null | undefined): boolean => {
if (url == null || url === '') {
return false
}
try {
const urlObj = new URL(url)
return urlObj.protocol.startsWith('http') && urlObj.pathname.length > 0
} catch (TypeError) {
return false
}
}

handleInfoButtonClick = (): void => {
const browser = detect()
const environment: {
Expand Down Expand Up @@ -184,7 +205,7 @@ class Header extends React.Component<HeaderProps, HeaderState> {
title: 'DICOM Tag Browser',
width,
content: <DicomTagBrowser
clients={this.props.clients}
clients={this.props.clients ?? {}}
studyInstanceUID={this.props.params.studyInstanceUID ?? ''}
/>,
onOk (): void {}
Expand Down Expand Up @@ -294,6 +315,58 @@ class Header extends React.Component<HeaderProps, HeaderState> {
this.setState({ isServerSelectionModalVisible: true })
}

handleServerSelectionInput = (
event: React.FormEvent<HTMLInputElement>
): void => {
const value = event.currentTarget.value
this.setState({
selectedServerUrl: value,
isServerSelectionDisabled: !this.isValidServerUrl(value)
})
}

handleServerSelectionCancellation = (): void => {
const cachedServerUrl = window.localStorage.getItem('slim_selected_server')
this.setState({
selectedServerUrl: cachedServerUrl ?? undefined,
isServerSelectionModalVisible: false,
isServerSelectionDisabled: !this.isValidServerUrl(cachedServerUrl)
})
}

handleServerSelectionModeChange = (e: any): void => {
const mode = e.target.value
window.localStorage.setItem('slim_server_selection_mode', mode)
this.setState({
serverSelectionMode: mode,
selectedServerUrl: mode === 'default' ? undefined : this.state.selectedServerUrl
})
}

handleServerSelection = (): void => {
if (this.state.serverSelectionMode === 'default') {
this.props.onServerSelection({ url: '' })
this.setState({
isServerSelectionModalVisible: false,
isServerSelectionDisabled: false
})
return
}

const url = this.state.selectedServerUrl
let closeModal = false
if (url != null && url !== '') {
if (url.startsWith('http://') || url.startsWith('https://')) {
this.props.onServerSelection({ url })
closeModal = true
}
}
this.setState({
isServerSelectionModalVisible: !closeModal,
isServerSelectionDisabled: !closeModal
})
}

render (): React.ReactNode {
let user = null
if (this.props.user !== undefined) {
Expand Down Expand Up @@ -375,56 +448,35 @@ class Header extends React.Component<HeaderProps, HeaderState> {
)
}

const handleServerSelectionInput = (
event: React.FormEvent<HTMLInputElement>
): void => {
const value = event.currentTarget.value
let isDisabled = true
if (value != null) {
try {
const url = new URL(value)
if (url.protocol.startsWith('http') && url.pathname.length > 0) {
isDisabled = false
}
} catch (TypeError) {}
}
this.setState({
selectedServerUrl: value,
isServerSelectionDisabled: isDisabled
})
}

const handleServerSelectionCancellation = (): void => {
this.setState({
selectedServerUrl: undefined,
isServerSelectionModalVisible: false,
isServerSelectionDisabled: true
})
}

const handleServerSelection = (): void => {
const url = this.state.selectedServerUrl
let closeModal = false
if (url != null && url !== '') {
if (url.startsWith('http://') || url.startsWith('https://')) {
this.props.onServerSelection({ url })
closeModal = true
}
}
this.setState({
selectedServerUrl: undefined,
isServerSelectionModalVisible: !closeModal,
isServerSelectionDisabled: true
})
}

const logoUrl = process.env.PUBLIC_URL + '/logo.svg'

const selectedServerUrl = this.state.serverSelectionMode === 'custom'
? this.state.selectedServerUrl
: this.props.clients?.default?.baseURL ?? this.props.defaultClients?.default?.baseURL
const urlInfo = selectedServerUrl != null && selectedServerUrl !== ''
? (
<Tooltip title={selectedServerUrl}>
<div
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
paddingRight: '20px',
paddingLeft: '20px'
}}
title={selectedServerUrl}
>
{selectedServerUrl}
</div>
</Tooltip>
)
: null

return (
<>
<Layout.Header style={{ width: '100%', padding: '0 14px' }}>
<Row>
<Col>
<Row style={{ flexWrap: 'nowrap' }}>
<Col style={{ flexShrink: 0 }}>
<Space align='center' direction='horizontal'>
<img
src={logoUrl}
Expand All @@ -433,8 +485,12 @@ class Header extends React.Component<HeaderProps, HeaderState> {
/>
</Space>
</Col>
<Col flex='auto' />
<Col>
<Col flex='auto' style={{ minWidth: 0, overflow: 'hidden' }}>
<div style={{ width: '100%', overflow: 'hidden' }}>
{this.props.showServerSelectionButton ? urlInfo : ''}
</div>
</Col>
<Col style={{ flexShrink: 0 }}>
<Space direction='horizontal'>
{worklistButton}
{infoButton}
Expand All @@ -450,19 +506,33 @@ class Header extends React.Component<HeaderProps, HeaderState> {
<Modal
open={this.state.isServerSelectionModalVisible}
title='Select DICOMweb server'
onOk={handleServerSelection}
onCancel={handleServerSelectionCancellation}
onOk={this.handleServerSelection}
onCancel={this.handleServerSelectionCancellation}
>
<Input
placeholder='Enter base URL of DICOMweb Study Service'
onChange={handleServerSelectionInput}
onPressEnter={handleServerSelection}
addonAfter={
this.state.isServerSelectionDisabled
? <StopOutlined style={{ color: 'rgba(0,0,0,.45)' }} />
: <CheckOutlined style={{ color: 'rgba(0,0,0,.45)' }} />
}
/>
<Radio.Group
value={this.state.serverSelectionMode}
onChange={this.handleServerSelectionModeChange}
style={{ marginBottom: '16px' }}
>
<Radio value='default'>Use default server</Radio>
<Radio value='custom'>Use custom server</Radio>
</Radio.Group>

{this.state.serverSelectionMode === 'custom' && (
<Tooltip title={this.state.selectedServerUrl}>
<Input
placeholder='Enter base URL of DICOMweb Study Service'
value={this.state.selectedServerUrl}
onChange={this.handleServerSelectionInput}
onPressEnter={this.handleServerSelection}
addonAfter={
this.state.isServerSelectionDisabled
? <StopOutlined style={{ color: 'rgba(0,0,0,.45)' }} />
: <CheckOutlined style={{ color: 'rgba(0,0,0,.45)' }} />
}
/>
</Tooltip>
)}
</Modal>
</>
)
Expand Down