initial structure for components rewrite

This commit is contained in:
Rodrigo Antinarelli authored and James A. Petts committed 2020-05-15 13:18:30 +02:00
1 parent 944afe4a3e
commit c391c007ff
284 files changed
+68087 -17215

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
import React from 'react';
import t from 'prop-types';
const kinds = {
info: '#5352ED',
positive: '#2ED573',
negative: '#FF4757',
warning: '#FFA502',
};
export const Alert = ({ children, kind, ...rest }) => (
<div
style={{
padding: 20,
borderRadius: 3,
color: 'white',
background: kinds[kind],
}}
{...rest}
>
{children}
</div>
);
Alert.propTypes = {
/**
* The kind prop is used to set the alert's background color
*/
kind: t.oneOf(['info', 'positive', 'negative', 'warning']),
};
Alert.defaultProps = {
kind: 'info',
};
+28
View File
@@ -0,0 +1,28 @@
---
name: Alert
menu: Components
---
import { Playground, Props } from 'docz'
import { Alert } from './Alert'
# Alert
## Properties
<Props of={Alert} />
## Basic usage
<Playground>
<Alert>Some message</Alert>
</Playground>
## Using different kinds
<Playground>
<Alert kind="info">Some message</Alert>
<Alert kind="positive">Some message</Alert>
<Alert kind="negative">Some message</Alert>
<Alert kind="warning">Some message</Alert>
</Playground>
@@ -0,0 +1,124 @@
import React from 'react';
import PropTypes from 'prop-types';
const Button = ({
children,
variant = defaults.variant,
color = defaults.color,
size = defaults.size,
radius = defaults.radius,
disabled = defaults.disabled,
elevation,
type = defaults.type,
startIcon: startIconProp,
endIcon: endIconProp,
className,
...rest
}) => {
const hasIcon = startIconProp || endIconProp;
const getClasses = () => {
const classes = [];
let buttonElevation;
if (variant === 'text') {
if (elevation) {
buttonElevation = elevation;
} else {
buttonElevation = false;
}
} else {
buttonElevation = defaults.elevation;
}
classes.push(variantClasses[variant][color]);
classes.push(radiusClasses[radius]);
classes.push(sizeClasses[size]);
classes.push(disabledClasses[disabled]);
classes.push(elevationClasses[buttonElevation]);
classes.push(className);
if (hasIcon) {
classes.push(baseIconClasses);
}
return classes.join(' ');
};
const baseIconClasses = 'inline-flex items-center';
const baseClasses =
'outline-none transition duration-300 ease-in-out rounded font-bold focus:outline-none';
const startIcon = startIconProp && (
<div className="mr-2">{startIconProp}</div>
);
const endIcon = endIconProp && <div className="ml-2">{endIconProp}</div>;
return (
<button className={`${baseClasses} ${getClasses()}`} type={type} {...rest}>
{startIcon}
{children}
{endIcon}
</button>
);
};
const defaults = {
variant: 'outlined',
color: 'default',
size: 'medium',
radius: 'medium',
disabled: false,
elevation: true,
type: 'button',
};
const radiusClasses = {
small: 'rounded-sm',
medium: 'rounded-md',
large: 'rounded-lg',
full: 'rounded-full',
};
const disabledClasses = {
true: 'cursor-not-allowed',
false: '',
};
const elevationClasses = {
true: 'shadow',
false: 'shadow-none',
};
const variantClasses = {
text: {
default: 'hover:bg-gray-200 text-black',
primary: 'hover:bg-blue-100 text-blue-900',
secondary: 'hover:bg-blue-100 text-blue-300',
},
outlined: {
default: 'border border-black text-black hover:bg-gray-200',
primary: 'border border-blue-900 text-blue-900 hover:bg-blue-100',
secondary: 'border border-blue-300 text-blue-300 hover:bg-blue-100',
},
contained: {
default: 'bg-black hover:bg-gray-800 text-white',
primary: 'bg-blue-900 hover:bg-blue-800 text-white',
secondary: 'bg-blue-300 hover:bg-blue-500 text-black',
},
};
const sizeClasses = {
small: 'py-1 px-2 text-sm',
medium: 'py-2 px-4 text-base',
large: 'py-3 px-6 text-lg',
};
Button.propTypes = {
size: PropTypes.oneOf(['small', 'medium', 'large']),
radius: PropTypes.oneOf(['small', 'medium', 'large', 'full']),
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
color: PropTypes.oneOf(['default', 'primary', 'secondary']),
};
export default Button;
@@ -0,0 +1,180 @@
---
name: Button
menu: Components
route: components/button
---
import { Playground, Props } from 'docz';
import Button from './';
# Button
Buttons are used to execute actions when users interacts with them.
## Properties
<Props of={Button} />
## Contained Buttons
<Playground>
<>
<Button variant="contained" style={{ margin: 5 }}>
Default
</Button>
<Button variant="contained" color="primary" style={{ margin: 5 }}>
Primary
</Button>
<Button variant="contained" color="secondary" style={{ margin: 5 }}>
Secondary
</Button>
</>
</Playground>
## Outlined Buttons
<Playground>
<>
<Button variant="outlined" style={{ margin: 5 }}>
Default
</Button>
<Button variant="outlined" color="primary" style={{ margin: 5 }}>
Primary
</Button>
<Button variant="outlined" color="secondary" style={{ margin: 5 }}>
Secondary
</Button>
</>
</Playground>
## Text Buttons
<Playground>
<>
<Button variant="text" style={{ margin: 5 }}>
Default
</Button>
<Button variant="text" color="primary" style={{ margin: 5 }}>
Primary
</Button>
<Button variant="text" color="secondary" style={{ margin: 5 }}>
Secondary
</Button>
</>
</Playground>
## Sizes
<Playground>
<>
<div>
<Button variant="text" size="small" style={{ margin: 5 }}>
Small
</Button>
<Button variant="contained" size="medium" style={{ margin: 5 }}>
Medium
</Button>
<Button variant="outlined" size="large" style={{ margin: 5 }}>
Large
</Button>
</div>
<div>
<Button
variant="text"
size="small"
color="primary"
style={{ margin: 5 }}
>
Small
</Button>
<Button
variant="contained"
size="medium"
color="primary"
style={{ margin: 5 }}
>
Medium
</Button>
<Button
variant="outlined"
size="large"
color="primary"
style={{ margin: 5 }}
>
large
</Button>
</div>
<div>
<Button
variant="text"
size="small"
color="secondary"
style={{ margin: 5 }}
>
Small
</Button>
<Button
variant="contained"
size="medium"
color="secondary"
style={{ margin: 5 }}
>
Medium
</Button>
<Button
variant="outlined"
size="large"
color="secondary"
style={{ margin: 5 }}
>
Large
</Button>
</div>
</>
</Playground>
## Buttons with icons
<Playground>
{() => {
return (
<div className="flex">
<Button
variant="contained"
color="primary"
style={{ margin: 5 }}
startIcon={
<svg
class="fill-current w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z" />
</svg>
}
>
Download
</Button>
<Button
variant="contained"
color="primary"
style={{ margin: 5 }}
endIcon={
<svg
class="fill-current w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z" />
</svg>
}
>
Download
</Button>
</div>
)
}}
</Playground>
@@ -0,0 +1,2 @@
import Button from './Button';
export default Button;
@@ -0,0 +1,17 @@
import React from 'react';
import Label from '../Label';
const Input = ({ label, ...rest }) => {
return (
<>
<Label text="Label">
<input
className="transition duration-300 shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
{...rest}
/>
</Label>
</>
);
};
export default Input;
@@ -0,0 +1,30 @@
---
name: Input
menu: Components
route: components/input
---
import { Playground, Props } from 'docz';
import Input from './';
# Input
## Properties
<Props of={Input} />
## Basic usage
<Playground>
{() => {
const [inputValue, setInputValue] = React.useState('');
return (
<Input
label="Label"
value={inputValue}
placeholder="Type something..."
onChange={e => setInputValue(e.target.value)}
/>
);
}}
</Playground>
@@ -0,0 +1,2 @@
import Input from './Input';
export default Input;
@@ -0,0 +1,34 @@
import React from 'react';
import t from 'prop-types';
const Label = ({ children, spacing, className, text, ...rest }) => {
const baseClasses = 'block text-gray-700 text-sm font-bold';
const getClasses = () => {
const classes = [];
classes.push(baseClasses);
classes.push(className);
return classes.join(' ');
};
return (
<div>
<label className={getClasses()}>
{text}
{children}
</label>
</div>
);
};
const spacing = {
small: 'mb-1',
medium: 'mb-2',
large: 'mb-3',
};
Label.defaultProps = {};
Label.prototypes = {};
export default Label;
@@ -0,0 +1,20 @@
---
name: Label
menu: Components
route: components/label
---
import { Playground, Props } from 'docz';
import Label from './';
# Label
## Properties
<Props of={Label} />
## Basic usage
<Playground>
<Label text="Label" />
</Playground>
@@ -0,0 +1,2 @@
import Label from './Label';
export default Label;
@@ -1,42 +0,0 @@
---
name: Checkbox
menu: Elements
route: /components/checkbox
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { Checkbox } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# Checkbox
## Basic usage
<Playground>
<State
initial={{
checked : false,
label : 'Sample check'
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '300px', margin: '0 auto' }}>
<Checkbox {...state} />
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={Checkbox} />
## Translation Namespace
<NameSpace name="Checkbox" />
@@ -1,56 +0,0 @@
.ohif-check-container {
--check-button-dim: 15px;
}
.ohif-check-container input {
position: absolute;
opacity: 0;
height: inherit;
width: inherit;
cursor: default;
}
.ohif-check-container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ohif-checkbox {
width: var(--check-button-dim);
height: var(--check-button-dim);
position: absolute;
top: 20%;
left: 5%;
cursor: pointer;
background-color: var(--ui-gray-lighter);
}
.ohif-checkbox:hover {
background-color: var(--default-color);
}
.ohif-checkbox:after {
content: '';
position: absolute;
width: 70%;
height: 70%;
}
.ohif-checkbox.ohif-checked:after {
display: block;
top: 14%;
left: 14%;
background: white;
}
.ohif-check-label {
font-size: 12px;
font-weight: 500;
}
@@ -1,58 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './checkbox.css';
export class Checkbox extends Component {
static propTypes = {
label: PropTypes.string.isRequired,
checked: PropTypes.bool,
onChange: PropTypes.func,
};
constructor(props) {
super(props);
this.state = { checked: !!props.checked, label: props.label };
}
handleChange(e) {
const checked = e.target.checked;
this.setState({ checked });
if (this.props.onChange) this.props.onChange(checked);
}
componentDidUpdate(props) {
const { checked = false, label } = props;
if (this.state.checked !== checked || this.state.label !== label) {
this.setState({
checked,
label,
});
}
}
render() {
let checkbox;
if (this.state.checked) {
checkbox = <span className="ohif-checkbox ohif-checked" />;
} else {
checkbox = <span className="ohif-checkbox" />;
}
return (
<div className="ohif-check-container">
<form>
<label className="ohif-check-label">
<input
type="checkbox"
checked={this.state.checked}
onChange={this.handleChange.bind(this)}
/>
{checkbox}
{this.state.label}
</label>
</form>
</div>
);
}
}
@@ -1 +0,0 @@
export { Checkbox } from './checkbox.js';
@@ -1,197 +0,0 @@
import './CineDialog.styl';
import React, { PureComponent } from 'react';
import { withTranslation } from '../../contextProviders';
import { Icon } from './../../elements/Icon';
import PropTypes from 'prop-types';
class CineDialog extends PureComponent {
constructor(props) {
super(props);
this.state = {
cineFrameRate: props.cineFrameRate,
isPlaying: props.isPlaying,
};
}
static propTypes = {
/** Minimum value for range slider */
cineMinFrameRate: PropTypes.number.isRequired,
/** Maximum value for range slider */
cineMaxFrameRate: PropTypes.number.isRequired,
/** Increment range slider can "step" in either direction. */
cineStepFrameRate: PropTypes.number.isRequired,
cineFrameRate: PropTypes.number.isRequired,
/** 'True' if playing, 'False' if paused. */
isPlaying: PropTypes.bool.isRequired,
onPlayPauseChanged: PropTypes.func,
onFrameRateChanged: PropTypes.func,
onClickNextButton: PropTypes.func,
onClickBackButton: PropTypes.func,
onClickSkipToStart: PropTypes.func,
onClickSkipToEnd: PropTypes.func,
/** i18next translation function */
t: PropTypes.func.isRequired,
};
static defaultProps = {
cineMinFrameRate: 1,
cineMaxFrameRate: 90,
cineStepFrameRate: 1,
cineFrameRate: 24,
isPlaying: false,
};
componentDidUpdate(prevProps) {
// TODO: Not sure if we should just switch this to a stateless
// fully-controlled component instead
if (
this.props.isPlaying !== prevProps.isPlaying ||
this.props.isPlaying !== this.state.isPlaying
) {
this.setState({
isPlaying: this.props.isPlaying,
});
}
if (
this.props.cineFrameRate !== prevProps.cineFrameRate ||
this.props.cineFrameRate !== this.state.cineFrameRate
) {
this.setState({
cineFrameRate: this.props.cineFrameRate,
});
}
}
handleInputChange = event => {
const target = event.target;
let value = target.value;
if (target.type === 'range') {
value = parseFloat(target.value);
}
const name = target.name;
this.setState({
[name]: value,
});
if (name === 'cineFrameRate' && this.props.onFrameRateChanged) {
this.props.onFrameRateChanged(parseFloat(value));
}
};
onClickPlayPause = () => {
const value = !this.state.isPlaying;
this.setState({
isPlaying: value,
});
if (this.props.onPlayPauseChanged) {
this.props.onPlayPauseChanged(value);
}
};
onClickNextButton = event => {
if (this.props.onClickNextButton) {
this.props.onClickNextButton(event);
}
};
onClickBackButton = event => {
if (this.props.onClickBackButton) {
this.props.onClickBackButton(event);
}
};
onClickSkipToStart = event => {
if (this.props.onClickSkipToStart) {
this.props.onClickSkipToStart(event);
}
};
onClickSkipToEnd = event => {
if (this.props.onClickSkipToEnd) {
this.props.onClickSkipToEnd(event);
}
};
render() {
const { t } = this.props;
return (
<div className="CineDialog">
<div className="noselect double-row-style">
<div className="cine-controls">
<div className="btn-group">
<button
title={t('Skip to first image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickSkipToStart}
>
<Icon name="fast-backward" />
</button>
<button
title={t('Previous image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickBackButton}
>
<Icon name="step-backward" />
</button>
<button
title={t('Play / Stop')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickPlayPause}
>
<Icon name={this.state.isPlaying ? 'stop' : 'play'} />
</button>
<button
title={t('Next image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickNextButton}
>
<Icon name="step-forward" />
</button>
<button
title={t('Skip to last image')}
className="btn"
data-toggle="tooltip"
onClick={this.onClickSkipToEnd}
>
<Icon name="fast-forward" />
</button>
</div>
</div>
<div className="cine-options">
<div className="fps-section">
<input
type="range"
name="cineFrameRate"
min={this.props.cineMinFrameRate}
max={this.props.cineMaxFrameRate}
step={this.props.cineStepFrameRate}
value={this.state.cineFrameRate}
onChange={this.handleInputChange}
/>
</div>
<span className="fps">
{this.state.cineFrameRate.toFixed(1)} {t('fps')}
</span>
</div>
</div>
</div>
);
}
}
const connectedComponent = withTranslation('CineDialog')(CineDialog);
export { connectedComponent as CineDialog };
export default connectedComponent;
@@ -1,172 +0,0 @@
@import './../../design/styles/common/global.styl'
@import './../../design/styles/common/form.styl'
.CineDialog
background: var(--ui-gray-darkest);
color: var(--text-secondary-color);
filter: drop-shadow(0 0 3px var(--ui-gray-darkest))
border: none
border-radius: 8px
width: inherit;
h5
font-size: 20px
line-height: 35px
margin: 0
h5, label
font-weight: 400
.cine-navigation, .cine-controls, .cine-options
cursor: default
.fps-section
input[type="range"]
background-color: transparent
border: 0 none
outline: 0 none
&::-ms-tooltip
display: none
.double-row-style
box-sizing: border-box
width: 290px
height: 70px
padding: 10px
top: 2%
left: 35%
.cine-navigation
position: absolute
right: 16px
top: 10px
.btn
padding: 0 4px
i
font-size: 32px
line-height: 32px
.cine-controls
left: 0px
.cine-options
padding: 0px 0
.fps-section
width: 175px
float: left
.fps
float: right
.single-row-style
box-sizing: content-box
width: 425px
height: 45px
padding: 4px 8px
top: 2%
left: 35%
.cine-navigation
float: right
overflow: hidden
padding-right: 12px
position: relative
.btn
padding: 0 2px
margin: 0 0 0 2px
border: 0 none
&:first-of-type
margin-left: 0
i
font-size: 32px
line-height: 45px
.cine-controls
position: relative
float: left
overflow: hidden
.btn
font-size: 22px
line-height: 45px
min-width: 28px
padding: 0 0px
margin: 0 4px
border: 0 none
.cine-options
display: block
position: relative
width: 150px
float: left
overflow: hidden
padding: 0
margin: 0 0 0 10px
.fps-section
display: block
float: left
width: 80px
input[type="range"]
line-height: 45px
height: 45px
.fps
display: block
width: 68px
float: left
padding: 0
margin: 0 0 0 2px
text-align: center
line-height: 45px
font-size: 14px;
line-height: 1.42857143;
.btn
color: var(--text-secondary-color);
background-color: transparent
border: 0
border-radius: 4px
font-size: 15px
width: 45px
font-weight: normal
height: 37px
line-height: 37px
padding: 0 12px
&:hover, &:active, &.active
color: var(--hover-color);
&[disabled]
&:hover
color: inherit
&:active
color: var(--text-secondary-color);
&.btn-primary, &.btn-secondary, &.btn-danger
transition: background-color 0.3s ease
&:hover, &:active, &:focus
opacity: 0.8
&.btn-primary
background-color: var(--active-color);
color: var(--primary-background-color)
&.btn-secondary
background-color: var(--text-secondary-color);
color: var(--primary-background-color)
&.btn-danger
background-color: var(--text-destructive-color);
color: var(--text-primary-color);
&.active
box-shadow: inset 1px 1px 2px var(--ui-gray-darkest)
@@ -1,57 +0,0 @@
---
name: CINE Dialog
menu: Components
route: /components/cine-dialog
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { CineDialog } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# CINE Dialog
## Basic usage
<Playground>
<State
initial={{
isPlaying: false,
cineFrameRate: 24,
isLoopEnabled: true,
lastChange: '',
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '300px', margin: '0 auto' }}>
<CineDialog
{...state}
onClickSkipToStart={() =>
setState({ lastChange: 'Clicked SkipToStart' })
}
onClickSkipToEnd={() =>
setState({ lastChange: 'Clicked SkipToEnd' })
}
onClickNextButton={() => setState({ lastChange: 'Clicked Next' })}
onClickBackButton={() => setState({ lastChange: 'Clicked Back' })}
onLoopChanged={value => setState({ isLoopEnabled: value })}
onFrameRateChanged={value => setState({ cineFrameRate: value })}
onPlayPauseChanged={() => setState({ isPlaying: !state.isPlaying })}
/>
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={CineDialog} />
## Translation Namespace
<NameSpace name="CineDialog" />
@@ -1,2 +0,0 @@
import CineDialog from './CineDialog';
export { CineDialog };
@@ -1,107 +0,0 @@
import React from 'react';
import detect from 'browser-detect';
import { useTranslation } from 'react-i18next';
import './AboutContent.styl';
const AboutContent = () => {
const { t } = useTranslation('AboutContent');
const { os, version, name } = detect();
const capitalize = s =>
s.substr(0, 1).toUpperCase() + s.substr(1).toLowerCase();
const itemsPreset = () => {
return [
{
name: t('Repository URL'),
value: 'https://github.com/OHIF/Viewers/',
link: 'https://github.com/OHIF/Viewers/',
},
{
name: t('Latest Master Commits'),
value: 'https://github.com/OHIF/Viewers/commits/master',
link: 'https://github.com/OHIF/Viewers/commits/master',
},
{
name: 'Version Number',
value: process.env.VERSION_NUMBER,
},
{
name: t('Build Number'),
value: process.env.BUILD_NUM,
},
{
name: t('Browser'),
value: `${capitalize(name)} ${version}`,
},
{
name: t('OS'),
value: os,
},
];
};
const renderTableRow = ({ name, value, link }) => (
<tr key={name} style={{ backgroundColor: 'transparent' }}>
<td>{name}</td>
<td>
{link ? (
<a target="_blank" rel="noopener noreferrer" href={link}>
{value}
</a>
) : (
value
)}
</td>
</tr>
);
return (
<div className="AboutContent" data-cy="about-modal">
<div className="btn-group">
<a
className="btn btn-default"
target="_blank"
rel="noopener noreferrer"
href="https://groups.google.com/forum/#!forum/cornerstone-platform"
>
{t('Visit the forum')}
</a>
{` `}
<a
className="btn btn-default"
target="_blank"
rel="noopener noreferrer"
href="https://github.com/OHIF/Viewers/issues/new/choose"
>
{t('Report an issue')}
</a>
{` `}
<a
className="btn btn-default"
target="_blank"
rel="noopener noreferrer"
href="http://ohif.org"
>
{t('More details')}
</a>
</div>
<div>
<h3>{t('Version Information')}</h3>
<table className="table table-responsive">
<thead>
<tr>
<th>{t('Name')}</th>
<th>{t('Value')}</th>
</tr>
</thead>
<tbody>{itemsPreset().map(item => renderTableRow(item))}</tbody>
</table>
</div>
</div>
);
};
export { AboutContent };
export default AboutContent;
@@ -1,20 +0,0 @@
@import '../../../design/styles/common/button.styl'
@import '../../../design/styles/common/table.styl'
.AboutContent
.btn
border-color: #ccc;
.table
thead:first-child
tr:first-child
th, td
border-top: 0;
thead, tbody, tfoot
tr
th, td
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
border-top: 1px solid #ddd;
@@ -1,431 +0,0 @@
import React, {
useRef,
useCallback,
useEffect,
useState,
createRef,
} from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import './ViewportDownloadForm.styl';
import { TextInput, Select, Icon } from '@ohif/ui';
import classnames from 'classnames';
const FILE_TYPE_OPTIONS = [
{
key: 'jpg',
value: 'jpg',
},
{
key: 'png',
value: 'png',
},
];
const DEFAULT_FILENAME = 'image';
const REFRESH_VIEWPORT_TIMEOUT = 1000;
const ViewportDownloadForm = ({
activeViewport,
onClose,
updateViewportPreview,
enableViewport,
disableViewport,
toggleAnnotations,
loadImage,
downloadBlob,
defaultSize,
minimumSize,
maximumSize,
canvasClass,
}) => {
const [t] = useTranslation('ViewportDownloadForm');
const [filename, setFilename] = useState(DEFAULT_FILENAME);
const [fileType, setFileType] = useState('jpg');
const [dimensions, setDimensions] = useState({
width: defaultSize,
height: defaultSize,
});
const [showAnnotations, setShowAnnotations] = useState(true);
const [keepAspect, setKeepAspect] = useState(true);
const [aspectMultiplier, setAspectMultiplier] = useState({
width: 1,
height: 1,
});
const [viewportElement, setViewportElement] = useState();
const [viewportElementDimensions, setViewportElementDimensions] = useState({
width: defaultSize,
height: defaultSize,
});
const [downloadCanvas, setDownloadCanvas] = useState({
ref: createRef(),
width: defaultSize,
height: defaultSize,
});
const [viewportPreview, setViewportPreview] = useState({
src: null,
width: defaultSize,
height: defaultSize,
});
const [error, setError] = useState({
width: false,
height: false,
filename: false,
});
const hasError = Object.values(error).includes(true);
const refreshViewport = useRef(null);
const downloadImage = () => {
downloadBlob(
filename || DEFAULT_FILENAME,
fileType,
viewportElement,
downloadCanvas.ref.current
);
};
/**
* @param {object} event - Input change event
* @param {string} dimension - "height" | "width"
*/
const onDimensionsChange = (event, dimension) => {
const oppositeDimension = dimension === 'height' ? 'width' : 'height';
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
const isEmpty = sanitizedTargetValue === '';
const newDimensions = { ...dimensions };
const updatedDimension = isEmpty
? ''
: Math.min(sanitizedTargetValue, maximumSize);
if (updatedDimension === dimensions[dimension]) {
return;
}
newDimensions[dimension] = updatedDimension;
if (keepAspect && newDimensions[oppositeDimension] !== '') {
newDimensions[oppositeDimension] = Math.round(
newDimensions[dimension] * aspectMultiplier[oppositeDimension]
);
}
// In current code, keepAspect is always `true`
// And we always start w/ a square width/height
setDimensions(newDimensions);
// Only update if value is non-empty
if (!isEmpty) {
setViewportElementDimensions(newDimensions);
setDownloadCanvas(state => ({
...state,
...newDimensions,
}));
}
};
const error_messages = {
width: t('minWidthError'),
height: t('minHeightError'),
filename: t('emptyFilenameError'),
};
const renderErrorHandler = errorType => {
if (!error[errorType]) {
return null;
}
return <div className="input-error">{error_messages[errorType]}</div>;
};
const onKeepAspectToggle = () => {
const { width, height } = dimensions;
const aspectMultiplier = { ...aspectMultiplier };
if (!keepAspect) {
const base = Math.min(width, height);
aspectMultiplier.width = width / base;
aspectMultiplier.height = height / base;
setAspectMultiplier(aspectMultiplier);
}
setKeepAspect(!keepAspect);
};
const validSize = value => (value >= minimumSize ? value : minimumSize);
const loadAndUpdateViewports = useCallback(async () => {
const { width: scaledWidth, height: scaledHeight } = await loadImage(
activeViewport,
viewportElement,
dimensions.width,
dimensions.height
);
toggleAnnotations(showAnnotations, viewportElement);
const scaledDimensions = {
height: validSize(scaledHeight),
width: validSize(scaledWidth),
};
setViewportElementDimensions(scaledDimensions);
setDownloadCanvas(state => ({
...state,
...scaledDimensions,
}));
const {
dataUrl,
width: viewportElementWidth,
height: viewportElementHeight,
} = await updateViewportPreview(
viewportElement,
downloadCanvas.ref.current,
fileType
);
setViewportPreview(state => ({
...state,
src: dataUrl,
width: validSize(viewportElementWidth),
height: validSize(viewportElementHeight),
}));
}, [
activeViewport,
viewportElement,
showAnnotations,
loadImage,
toggleAnnotations,
updateViewportPreview,
fileType,
downloadCanvas.ref,
minimumSize,
maximumSize,
viewportElementDimensions,
]);
useEffect(() => {
enableViewport(viewportElement);
return () => {
disableViewport(viewportElement);
};
}, [disableViewport, enableViewport, viewportElement]);
useEffect(() => {
if (refreshViewport.current !== null) {
clearTimeout(refreshViewport.current);
}
refreshViewport.current = setTimeout(() => {
refreshViewport.current = null;
loadAndUpdateViewports();
}, REFRESH_VIEWPORT_TIMEOUT);
}, [
activeViewport,
viewportElement,
showAnnotations,
dimensions,
loadImage,
toggleAnnotations,
updateViewportPreview,
fileType,
downloadCanvas.ref,
minimumSize,
maximumSize,
]);
useEffect(() => {
const { width, height } = dimensions;
const hasError = {
width: width < minimumSize,
height: height < minimumSize,
filename: !filename,
};
setError({ ...hasError });
}, [dimensions, filename, minimumSize]);
return (
<div className="ViewportDownloadForm">
<div className="title">{t('formTitle')}</div>
<div className="file-info-container" data-cy="file-info-container">
<div className="dimension-wrapper">
<div className="dimensions">
<div className="width">
<TextInput
type="number"
min={minimumSize}
max={maximumSize}
value={dimensions.width}
label={t('imageWidth')}
onChange={evt => onDimensionsChange(evt, 'width')}
data-cy="image-width"
/>
{renderErrorHandler('width')}
</div>
<div className="height">
<TextInput
type="number"
min={minimumSize}
max={maximumSize}
value={dimensions.height}
label={t('imageHeight')}
onChange={evt => onDimensionsChange(evt, 'height')}
data-cy="image-height"
/>
{renderErrorHandler('height')}
</div>
</div>
<div className="keep-aspect-wrapper">
<button
id="keep-aspect"
className={classnames(
'form-button btn',
keepAspect ? 'active' : ''
)}
data-cy="keep-aspect"
alt={t('keepAspectRatio')}
onClick={onKeepAspectToggle}
>
<Icon
name={keepAspect ? 'link' : 'unlink'}
alt={keepAspect ? 'Dismiss Aspect' : 'Keep Aspect'}
/>
</button>
</div>
</div>
<div className="col">
<div className="file-name">
<TextInput
type="text"
data-cy="file-name"
value={filename}
onChange={event => setFilename(event.target.value)}
label={t('filename')}
id="file-name"
/>
{renderErrorHandler('filename')}
</div>
<div className="file-type">
<Select
value={fileType}
data-cy="file-type"
onChange={event => setFileType(event.target.value)}
options={FILE_TYPE_OPTIONS}
label={t('fileType')}
/>
</div>
</div>
<div className="col">
<div className="show-annotations">
<label htmlFor="show-annotations" className="form-check-label">
<input
id="show-annotations"
data-cy="show-annotations"
type="checkbox"
className="form-check-input"
checked={showAnnotations}
onChange={event => setShowAnnotations(event.target.checked)}
/>
{t('showAnnotations')}
</label>
</div>
</div>
</div>
<div
style={{
height: viewportElementDimensions.height,
width: viewportElementDimensions.width,
position: 'absolute',
left: '9999px',
}}
ref={ref => setViewportElement(ref)}
>
<canvas
className={canvasClass}
style={{
height: downloadCanvas.height,
width: downloadCanvas.width,
display: 'block',
}}
width={downloadCanvas.width}
height={downloadCanvas.height}
ref={downloadCanvas.ref}
></canvas>
</div>
{viewportPreview.src ? (
<div className="preview" data-cy="image-preview">
<div className="preview-header"> {t('imagePreview')}</div>
<img
className="viewport-preview"
src={viewportPreview.src}
alt={t('imagePreview')}
data-cy="image-preview"
data-cy="viewport-preview-img"
/>
</div>
) : (
<div className="loading-image">
<Icon name="circle-notch" className="icon-spin" />
{t('loadingPreview')}
</div>
)}
<div className="actions">
<div className="action-cancel">
<button
type="button"
data-cy="cancel-btn"
className="btn btn-danger"
onClick={onClose}
>
{t('Buttons:Cancel')}
</button>
</div>
<div className="action-save">
<button
disabled={hasError}
onClick={downloadImage}
className="btn btn-primary"
data-cy="download-btn"
>
{t('Buttons:Download')}
</button>
</div>
</div>
</div>
);
};
ViewportDownloadForm.propTypes = {
onClose: PropTypes.func.isRequired,
activeViewport: PropTypes.object,
updateViewportPreview: PropTypes.func.isRequired,
enableViewport: PropTypes.func.isRequired,
disableViewport: PropTypes.func.isRequired,
toggleAnnotations: PropTypes.func.isRequired,
loadImage: PropTypes.func.isRequired,
downloadBlob: PropTypes.func.isRequired,
/** A default width & height, between the minimum and maximum size */
defaultSize: PropTypes.number.isRequired,
minimumSize: PropTypes.number.isRequired,
maximumSize: PropTypes.number.isRequired,
canvasClass: PropTypes.string.isRequired,
};
export default ViewportDownloadForm;
@@ -1,140 +0,0 @@
@import '../../../design/styles/common/global.styl'
@import '../../../design/styles/common/form.styl'
@import '../../../design/styles/common/button.styl'
.ViewportDownloadForm
display: flex;
flex-direction: column;
z-index: 1080 !important;
input, select
max-height: 30px;
#keep-aspect svg
margin-top: 3px;
.title
margin: 0;
font-weight: bold;
.file-info-container
display: flex;
flex-direction: row;
justify-content: space-around;
margin: 20px 0;
border-radius: 5px;
padding: 20px 10px;
background-color: var(--ui-gray-dark);
@media screen and (max-width: 1023px)
flex-direction: column;
align-items: flex-start;
.width,
.height,
.file-name,
.file-type
height: 56px;
.input-ohif
margin: 0 5px;
.file-name,
.file-type
.select-ohif, .input-ohif
width: 170px;
.input-ohif-label, .select-ohif-label
width: 90px;
display: inline-block;
@media screen and (max-width: 1023px)
width: 120px;
.dimension-wrapper
display: flex;
flex-direction: row;
.dimensions
display: flex;
flex-direction: column;
.input-ohif-label
width: 120px;
display: inline-block;
.input-ohif
@media screen and (max-width: 1023px)
width: 170px;
.keep-aspect-wrapper
display: flex;
justify-content: center;
align-items: center;
padding: 0 10px;
height: 86px;
.show-annotations
font-weight: bold;
line-height: 30px;
input
margin-right: 7px;
vertical-align: middle;
label
display: flex;
justify-content: center;
align-items: center;
.loading-image
height: 580px;
display: flex;
justify-content: center;
align-items: center;
color: var(--active-color);
font-size: 20px;
.icon-spin
margin-right: 15px;
.preview
display: flex;
flex-direction: column;
background-color: var(--ui-gray-dark);
padding: 10px;
border-radius: 5px;
justify-content: flex-start;
align-items: center;
height: 580px;
.viewport-preview
max-height: 512px;
max-width: 512px;
.preview-header
width: 100%;
text-align center;
font-size: 1.3em;
margin: 0 0 10px;
.actions
display: flex;
flex-wrap: nowrap;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
.btn
margin: 0 10px;
.input-error
font-size: 12px;
color: red;
text-align: center;
margin: 3px 0;
.modal-dialog
height: 100%;
.modal-body
display: flex;
flex-direction: column;
@@ -1,39 +0,0 @@
---
name: Viewport Download Form
menu: Components
route: /components/viewport-download-form
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { ViewportDownloadForm } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# Viewport Download Form
## Basic usage
<Playground>
<State
initial={{}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<ViewportDownloadForm />
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={ViewportDownloadForm} />
## Translation Namespace
<NameSpace name="ViewportDownloadForm" />
@@ -1,2 +0,0 @@
import ViewportDownloadForm from './ViewportDownloadForm';
export { ViewportDownloadForm };
@@ -1,33 +0,0 @@
.ContextMenu {
position: relative;
background-color: white;
border-radius: 5px;
z-index: 1000;
display: block;
width: 170px;
}
.ContextMenu>ul {
list-style-type: none;
padding-left: 0;
margin: 0;
}
.ContextMenu>ul>li>button {
padding: 10px;
font-size: 14px;
border: none;
color: #516873;
border-radius: 3px;
outline: none;
cursor: pointer;
background: none;
}
.ContextMenu>ul>li>button:hover {
color: #16202b;
}
.ContextMenu>ul>li>button:active {
color: #79f9fe;
}
@@ -1 +0,0 @@
export { default as ContextMenu } from './ContextMenu.js';
@@ -1,4 +0,0 @@
.language-select
color: var(--primary-background-color)
display: block
min-width: 150px
@@ -1,64 +0,0 @@
import React, { PureComponent } from 'react';
import { LayoutChooser } from './LayoutChooser.js';
import PropTypes from 'prop-types';
import ToolbarButton from '../../viewer/ToolbarButton';
export class LayoutButton extends PureComponent {
static defaultProps = {
dropdownVisible: false,
};
static propTypes = {
dropdownVisible: PropTypes.bool.isRequired,
/** Called with the selectedCell number when grid sell is selected */
onChange: PropTypes.func,
/** The cell to show as selected */
selectedCell: PropTypes.object,
};
state = {
dropdownVisible: this.props.dropdownVisible,
};
componentDidUpdate(prevProps) {
if (this.props.dropdownVisible !== prevProps.dropdownVisible) {
this.setState({
dropdownVisible: this.props.dropdownVisible,
});
}
}
onClick = () => {
this.setState({
dropdownVisible: !this.state.dropdownVisible,
});
};
onChange = selectedCell => {
if (this.props.onChange) {
this.props.onChange(selectedCell);
}
};
render() {
return (
<div className="btn-group">
<ToolbarButton
isActive={this.state.dropdownVisible}
label={'Layout'}
icon="th"
onClick={this.onClick}
/>
<LayoutChooser
visible={this.state.dropdownVisible}
onChange={this.onChange}
onClick={this.onClick}
selectedCell={this.props.selectedCell}
/>
</div>
);
}
}
export default LayoutButton;
@@ -1,47 +0,0 @@
@import './../../design/styles/common/button.styl'
@import './../../design/styles/common/global.styl'
$borderColor = rgba(77, 99, 110, 0.81)
.layoutChooser-dropdown-menu
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
box-shadow: 0 6px 12px rgba(0,0,0,.175);
.layoutChooser
.selectedBefore
background-color: #5cc3eb
border: 1px solid $borderColor;
border-radius: 8px
padding: 5px 0;
position: absolute;
z-index: 5000
table
margin: 0 auto
border-spacing: 0;
border-collapse: collapse;
td
cursor: pointer
transition(background-color 0.1s ease)
&:hover, &.hover // Add the hover class here to be triggered by mouseenter/mouseleave
background-color: #209ac9
@@ -1,10 +0,0 @@
.LayoutExample {
background-color: var(--primary-background-color);
width: 160px;
height: 160px;
}
.GridLayout td {
padding: 5px;
border: 1px solid black;
}
@@ -1,49 +0,0 @@
---
name: Layout Button
menu: Components
route: /components/layout-button
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { LayoutButton } from './../index.js'
import './layoutButton.css'
import NameSpace from '../../../__docs__/NameSpace'
# Layout Button
## Basic usage
<Playground>
<State initial={{
selectedCell: {
className: 'hover',
col: 1,
row: 1
},
}}>
{({ state, setState }) => (
<React.Fragment>
{/* STATE */}
<div><pre>{JSON.stringify(state, null, 2) }</pre></div>
{/* COMPONENT */}
<div className="LayoutExample">
<LayoutButton
{...state}
onChange={cell => setState({ selectedCell: cell })}
/>
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={LayoutButton} />
## Translation Namespace
<NameSpace name="Buttons" />
@@ -1,2 +0,0 @@
export { LayoutButton } from './LayoutButton.js';
export { LayoutChooser } from './LayoutChooser.js';
@@ -1,218 +0,0 @@
import './MeasurementTable.styl';
import React, { Component } from 'react';
import { withTranslation } from '../../contextProviders';
import { Icon } from './../../elements/Icon';
import { MeasurementTableItem } from './MeasurementTableItem.js';
import { OverlayTrigger } from './../overlayTrigger';
import PropTypes from 'prop-types';
import { ScrollableArea } from './../../ScrollableArea/ScrollableArea.js';
import { TableList } from './../tableList';
import { Tooltip } from './../tooltip';
class MeasurementTable extends Component {
static propTypes = {
measurementCollection: PropTypes.array.isRequired,
timepoints: PropTypes.array.isRequired,
overallWarnings: PropTypes.object.isRequired,
readOnly: PropTypes.bool,
onItemClick: PropTypes.func,
onRelabelClick: PropTypes.func,
onDeleteClick: PropTypes.func,
onEditDescriptionClick: PropTypes.func,
selectedMeasurementNumber: PropTypes.number,
t: PropTypes.func,
saveFunction: PropTypes.func,
onSaveComplete: PropTypes.func,
};
static defaultProps = {
overallWarnings: {
warningList: [],
},
readOnly: false,
};
state = {
selectedKey: null,
};
render() {
const { overallWarnings, saveFunction, t } = this.props;
const hasOverallWarnings = overallWarnings.warningList.length > 0;
return (
<div className="measurementTable">
<div className="measurementTableHeader">
{hasOverallWarnings && (
<OverlayTrigger
key={'overwall-warning'}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
style={{}}
>
<div className="warningTitle">
{t('Criteria nonconformities')}
</div>
<div className="warningContent">
{this.getWarningContent()}
</div>
</Tooltip>
}
>
<span className="warning-status">
<span className="warning-border">
<Icon name="exclamation-triangle" />
</span>
</span>
</OverlayTrigger>
)}
{this.getTimepointsHeader()}
</div>
<ScrollableArea>
<div>{this.getMeasurementsGroups()}</div>
</ScrollableArea>
<div className="measurementTableFooter">
{saveFunction && (
<button
onClick={this.saveFunction}
className="saveBtn"
data-cy="save-measurements-btn"
>
<Icon name="save" width="14px" height="14px" />
Save measurements
</button>
)}
</div>
</div>
);
}
saveFunction = async event => {
const { saveFunction, onSaveComplete } = this.props;
if (saveFunction) {
try {
const result = await saveFunction();
if (onSaveComplete) {
onSaveComplete({
title: 'STOW SR',
message: result.message,
type: 'success',
});
}
} catch (error) {
if (onSaveComplete) {
onSaveComplete({
title: 'STOW SR',
message: error.message,
type: 'error',
});
}
}
}
};
getMeasurementsGroups = () => {
return this.props.measurementCollection.map((measureGroup, index) => {
return (
<TableList
key={index}
customHeader={this.getCustomHeader(measureGroup)}
>
{this.getMeasurements(measureGroup)}
</TableList>
);
});
};
getMeasurements = measureGroup => {
const selectedKey = this.props.selectedMeasurementNumber
? this.props.selectedMeasurementNumber
: this.state.selectedKey;
return measureGroup.measurements.map((measurement, index) => {
const key = measurement.measurementNumber;
const itemIndex = measurement.itemNumber || index + 1;
const itemClass =
selectedKey === key && !this.props.readOnly ? 'selected' : '';
return (
<MeasurementTableItem
key={key}
itemIndex={itemIndex}
itemClass={itemClass}
measurementData={measurement}
onItemClick={this.onItemClick}
onRelabel={this.props.onRelabelClick}
onDelete={this.props.onDeleteClick}
onEditDescription={this.props.onEditDescriptionClick}
/>
);
});
};
onItemClick = (event, measurementData) => {
if (this.props.readOnly) return;
this.setState({
selectedKey: measurementData.measurementNumber,
});
if (this.props.onItemClick) {
this.props.onItemClick(event, measurementData);
}
};
getCustomHeader = measureGroup => {
return (
<React.Fragment>
<div className="tableListHeaderTitle">
{this.props.t(measureGroup.groupName)}
</div>
{measureGroup.maxMeasurements && (
<div className="maxMeasurements">
{this.props.t('MAX')} {measureGroup.maxMeasurements}
</div>
)}
<div className="numberOfItems">{measureGroup.measurements.length}</div>
</React.Fragment>
);
};
getTimepointsHeader = () => {
const { timepoints, t } = this.props;
return timepoints.map((timepoint, index) => {
return (
<div key={index} className="measurementTableHeaderItem">
<div className="timepointLabel">{t(timepoint.key)}</div>
<div className="timepointDate">{timepoint.date}</div>
</div>
);
});
};
getWarningContent = () => {
const { warningList = '' } = this.props.overallWarnings;
if (Array.isArray(warningList)) {
const listedWarnings = warningList.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{warningList}</React.Fragment>;
}
};
}
const connectedComponent = withTranslation(['MeasurementTable', 'Common'])(
MeasurementTable
);
export { connectedComponent as MeasurementTable };
export default connectedComponent;
@@ -1,116 +0,0 @@
.measurementTable
background-color: var(--primary-background-color)
height: 100%
width: 100%
display: flex
flex-direction: column
.maxMeasurements
border-radius: 3px
color: var(--primary-background-color)
font-size: 12px
font-weight: 500
height: 19px
line-height: 17px
margin-top: 22px
margin-right: 15px
padding: 2px 6px 0
text-transform: uppercase
transition: all 0.3s ease
background-color: var(--default-color)
.measurementTableHeader
padding-left: 45px
.measurementTableHeaderItem
display: inline-block
margin-top: 9px
margin-left: 9px
padding-left: 9px
width: 90px
font-size: 14px
line-height: 20px
border-left: 1px solid var(--text-secondary-color)
color: var(--text-primary-color)
.timepointLabel
color: var(--text-secondary-color)
font-size: 12px
.warning-status
display: block
position: absolute
width: 50px
height: 40px
padding-left: 7px
left: 15px
top: 7px
.warning-border
display: block
border: 2px solid #e29e4a
border-radius: 36px
padding: 6px 7px
width: 40px
height: 40px
svg
color: #e29e4a
width: 22px
height: 20px
pointer-events: inherit
.measurementTableFooter
padding: 10px
color: #fff
text-align: center
z-index: 1
.saveBtn
border: 1px solid var(--ui-border-color-active)
border-radius: 25px
background-color: var(--active-color)
color: var(--ui-gray-dark)
line-height: 25px
padding: 0 20px
outline: none
cursor: pointer
&:hover, &:active
background-color: var(--ui-sky-blue)
svg
margin-right: 4px
position: relative
top: 2px
.tooltip-warning
&.in
opacity 1
.tooltip-inner
padding: 0
.warningTitle
text-align: left
background-color: var(--ui-gray-darkest)
padding: 7px 15px
border-top-left-radius: 7px
border-top-right-radius: 7px
.warningContent
text-align center
background-color: var(--ui-gray)
border-bottom-left-radius: 7px
border-bottom-right-radius: 7px
ol
margin-top: auto
padding-inline-start: 20px
padding-top: 10px
padding-bottom: 10px
padding-left: 25px
text-align: left
li
padding-right: 10px
@@ -1,165 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from '../../contextProviders';
import { Icon } from './../../elements/Icon';
import { OverlayTrigger } from './../overlayTrigger';
import { Tooltip } from './../tooltip';
import { TableListItem } from './../tableList/TableListItem.js';
import './MeasurementTableItem.styl';
class MeasurementTableItem extends Component {
static propTypes = {
measurementData: PropTypes.object.isRequired,
onItemClick: PropTypes.func.isRequired,
onRelabel: PropTypes.func,
onDelete: PropTypes.func,
onEditDescription: PropTypes.func,
itemClass: PropTypes.string,
itemIndex: PropTypes.number,
t: PropTypes.func,
};
render() {
const { warningTitle = '', hasWarnings } = this.props.measurementData;
return (
<React.Fragment>
{hasWarnings ? (
<OverlayTrigger
key={this.props.itemIndex}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">{this.props.t(warningTitle)}</div>
<div className="warningContent">{this.getWarningContent()}</div>
</Tooltip>
}
>
<div>{this.getTableListItem()}</div>
</OverlayTrigger>
) : (
<React.Fragment>{this.getTableListItem()}</React.Fragment>
)}
</React.Fragment>
);
}
getActionButton = (btnLabel, onClickCallback) => {
return (
<button key={btnLabel} className="btnAction" onClick={onClickCallback}>
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
{this.props.t(btnLabel)}
</button>
);
};
getTableListItem = () => {
const hasWarningClass = this.props.measurementData.hasWarnings
? 'hasWarnings'
: '';
const actionButtons = [];
if (typeof this.props.onRelabel === 'function') {
const relabelButton = this.getActionButton(
'Relabel',
this.onRelabelClick
);
actionButtons.push(relabelButton);
}
if (typeof this.props.onEditDescription === 'function') {
const descriptionButton = this.getActionButton(
'Description',
this.onEditDescriptionClick
);
actionButtons.push(descriptionButton);
}
if (typeof this.props.onDelete === 'function') {
const deleteButton = this.getActionButton('Delete', this.onDeleteClick);
actionButtons.push(deleteButton);
}
return (
<TableListItem
itemKey={this.props.measurementData.measurementNumber}
itemClass={`measurementItem ${this.props.itemClass} ${hasWarningClass}`}
itemIndex={this.props.itemIndex}
onItemClick={this.onItemClick}
>
<div>
<div className="measurementLocation">
{this.props.t(this.props.measurementData.label, {
keySeparator: '>',
nsSeparator: '|',
})}
</div>
<div className="displayTexts">{this.getDataDisplayText()}</div>
<div className="rowActions">{actionButtons}</div>
</div>
</TableListItem>
);
};
onItemClick = event => {
this.props.onItemClick(event, this.props.measurementData);
};
onRelabelClick = event => {
// Prevent onItemClick from firing
event.stopPropagation();
this.props.onRelabel(event, this.props.measurementData);
};
onEditDescriptionClick = event => {
// Prevent onItemClick from firing
event.stopPropagation();
this.props.onEditDescription(event, this.props.measurementData);
};
onDeleteClick = event => {
// Prevent onItemClick from firing
event.stopPropagation();
this.props.onDelete(event, this.props.measurementData);
};
getDataDisplayText = () => {
return this.props.measurementData.data.map((data, index) => {
return (
<div key={`displayText_${index}`} className="measurementDisplayText">
{data.displayText ? data.displayText : '...'}
</div>
);
});
};
getWarningContent = () => {
const { warningList = '' } = this.props.measurementData;
if (Array.isArray(warningList)) {
const listedWarnings = warningList.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{warningList}</React.Fragment>;
}
};
}
const connectedComponent = withTranslation('MeasurementTable')(
MeasurementTableItem
);
export { connectedComponent as MeasurementTableItem };
export default connectedComponent;
@@ -1,49 +0,0 @@
---
name: Measurement Table
menu: Components
route: /components/measurement-table
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { MeasurementTable } from './../index.js'
import NameSpace from '../../../__docs__/NameSpace'
//
import measurements from './measurements.js'
import timepoints from './timepoints.js'
import warnings from './warnings.js'
# Measurement Table
## Basic usage
<Playground>
<State
initial={{
// Spelling error: overwallWarnings
overwallWarnings: warnings,
timepoints,
measurementCollection: measurements,
}}
>
{({ state, setState }) => (
<div style={{ height: '340px' }}>
<MeasurementTable
{...state}
onItemClick={() => alert('onItemClick')}
onRelabelClick={() => alert('onRelabelClick')}
onDeleteClick={() => alert('onDeleteClick')}
onEditDescriptionClick={() => alert('onEditDescriptionClick')}
/>
</div>
)}
</State>
</Playground>
## API
<Props of={MeasurementTable} />
## Translation Namespace
<NameSpace name="MeasurementTable" />
@@ -1,93 +0,0 @@
export default [
{
maxMeasurements: 5,
groupName: 'Targets',
measurements: [
{
measurementId: '123',
measurementNumber: '123',
itemNumber: 1,
label: 'Chest Wall Posterior',
hasWarnings: true,
warningTitle: 'Criteria nonconformities',
isSplitLesion: false,
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
data: [
{
displayText: '25.7 x 12.9',
},
{
displayText: '24.7 x 11.5',
},
{},
],
},
{
measurementId: '124',
measurementNumber: '124',
itemNumber: 2,
label: 'Bone Extremity',
data: [
{
displayText: '24.7 x 11.1',
},
{
displayText: '21.2 x 10.9',
},
{},
],
},
],
},
{
maxMeasurements: 3,
groupName: 'NonTargets',
measurements: [
{
measurementId: '125',
measurementNumber: '125',
itemNumber: 1,
label: 'Chest Wall Single Site',
data: [
{
displayText: 'MD',
},
{
displayText: 'NM',
},
{},
],
},
{
measurementId: '126',
measurementNumber: '126',
itemNumber: 2,
label: 'Extremity Multiple Sites',
data: [
{
displayText: 'CR',
},
{},
{},
],
},
{
measurementId: '127',
measurementNumber: '127',
label: 'Extremity Site',
data: [
{
displayText: 'CR',
},
{},
{
displayText: 'NM',
},
],
},
],
},
];
@@ -1,14 +0,0 @@
export default [
{
label: 'Follow-up 2',
date: '20-Dec-18',
},
{
label: 'Follow-up 1',
date: '15-Jun-18',
},
{
label: 'Baseline',
date: '10-Apr-18',
},
];
@@ -1,6 +0,0 @@
export default {
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
};
@@ -1,2 +0,0 @@
export { MeasurementTable } from './MeasurementTable.js';
export { MeasurementTableItem } from './MeasurementTableItem.js';
@@ -1,76 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-modal';
import classNames from 'classnames';
import './OHIFModal.styl';
const customStyle = {
overlay: {
zIndex: 1071,
backgroundColor: 'rgb(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
};
Modal.setAppElement(document.getElementById('root'));
const OHIFModal = ({
className,
closeButton,
shouldCloseOnEsc,
isOpen,
title,
onClose,
children,
}) => {
const renderHeader = () => {
return (
title && (
<div className="OHIFModal__header" data-cy="modal-header">
<h4>{title}</h4>
{closeButton && (
<button data-cy="close-button" onClick={onClose}>
×
</button>
)}
</div>
)
);
};
return (
<Modal
className={classNames('OHIFModal', className)}
data-cy="modal"
shouldCloseOnEsc={shouldCloseOnEsc}
isOpen={isOpen}
title={title}
style={customStyle}
>
<>
{renderHeader()}
<div className="OHIFModal__content" data-cy="modal-content">
{children}
</div>
</>
</Modal>
);
};
OHIFModal.propTypes = {
className: PropTypes.string,
closeButton: PropTypes.bool,
shouldCloseOnEsc: PropTypes.bool,
isOpen: PropTypes.bool,
title: PropTypes.string,
onClose: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
export default OHIFModal;
@@ -1,67 +0,0 @@
.OHIFModal
background-color: var(--ui-gray-darker)
border-color: var(--ui-border-color)
color: var(--text-secondary-color)
border-radius: 6px
border: 0
color: var(--text-primary-color)
position: relative
-webkit-box-shadow: 0 3px 9px rgba(0,0,0,.5)
box-shadow: 0 3px 9px rgba(0,0,0,.5);
background-clip: padding-box
outline: 0
@media (min-width: 320px)
width: 78%
min-width: 300px
@media (min-width: 768px)
width: 600px
@media (min-width: 992px)
width: 900px
&__content
padding: 20px
max-height: 90vh;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar
display: none;
&__header
display: flex
justify-content: space-between
align-items: center
border-bottom-width: 3px
border-bottom-style: solid
border-bottom-color: #000000
padding: 20px
position: relative
h4
color: var(--text-secondary-color)
font-size: 20px
font-weight: 500
line-height: 24px
padding-right: 24px
margin: 0
button
cursor: pointer
margin: -10px 0 0 0
padding: 0
background-color: transparent
border: none
color: var(--text-secondary-color)
font-size: 25px
font-weight: lighter
&:active,
&:focus,
&:focus:active
background-image: none
outline: 0
box-shadow: none
@@ -1,39 +0,0 @@
---
name: OHIFModal
menu: Components
route: /components/ohif-modal
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { OHIFModal } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
# OHIF Modal
## Basic usage
<Playground>
<State
initial={{}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<OHIFModal />
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={OHIFModal} />
## Translation Namespace
<NameSpace name="OHIFModal" />
@@ -1,2 +0,0 @@
import OHIFModal from './OHIFModal';
export { OHIFModal };
@@ -1,102 +0,0 @@
import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import Transition, {
ENTERED,
ENTERING,
} from 'react-transition-group/Transition';
const propTypes = {
/**
* Show the component; triggers the fade in or fade out animation
*/
in: PropTypes.bool,
/**
* Wait until the first "enter" transition to mount the component (add it to the DOM)
*/
mountOnEnter: PropTypes.bool,
/**
* Unmount the component (remove it from the DOM) when it is faded out
*/
unmountOnExit: PropTypes.bool,
/**
* Run the fade in animation when the component mounts, if it is initially
* shown
*/
appear: PropTypes.bool,
/**
* Duration of the fade animation in milliseconds, to ensure that finishing
* callbacks are fired even if the original browser transition end events are
* canceled
*/
timeout: PropTypes.number,
/**
* Callback fired before the component fades in
*/
onEnter: PropTypes.func,
/**
* Callback fired after the component starts to fade in
*/
onEntering: PropTypes.func,
/**
* Callback fired after the has component faded in
*/
onEntered: PropTypes.func,
/**
* Callback fired before the component fades out
*/
onExit: PropTypes.func,
/**
* Callback fired after the component starts to fade out
*/
onExiting: PropTypes.func,
/**
* Callback fired after the component has faded out
*/
onExited: PropTypes.func,
};
const defaultProps = {
in: false,
timeout: 300,
mountOnEnter: false,
unmountOnExit: false,
appear: false,
};
const fadeStyles = {
[ENTERING]: 'in',
[ENTERED]: 'in',
};
class Fade extends React.Component {
render() {
const { className, children, ...props } = this.props;
return (
<Transition {...props}>
{(status, innerProps) =>
React.cloneElement(children, {
...innerProps,
className: classNames(
'fade',
className,
children.props.className,
fadeStyles[status]
),
})
}
</Transition>
);
}
}
Fade.propTypes = propTypes;
Fade.defaultProps = defaultProps;
export default Fade;
@@ -1,102 +0,0 @@
import classNames from 'classnames';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import { Overlay as BaseOverlay } from 'react-overlays';
import elementType from 'prop-types-extra/lib/elementType';
import { withTranslation } from '../../contextProviders';
import Fade from './Fade';
const propTypes = {
/**
* Set the visibility of the Overlay
*/
show: PropTypes.bool,
/**
* Specify whether the overlay should trigger onHide when the user clicks outside the overlay
*/
rootClose: PropTypes.bool,
/**
* A callback invoked by the overlay when it wishes to be hidden. Required if
* `rootClose` is specified.
*/
onHide: PropTypes.func,
/**
* Use animation
*/
animation: PropTypes.oneOfType([PropTypes.bool, elementType]),
/**
* Callback fired before the Overlay transitions in
*/
onEnter: PropTypes.func,
/**
* Callback fired as the Overlay begins to transition in
*/
onEntering: PropTypes.func,
/**
* Callback fired after the Overlay finishes transitioning in
*/
onEntered: PropTypes.func,
/**
* Callback fired right before the Overlay transitions out
*/
onExit: PropTypes.func,
/**
* Callback fired as the Overlay begins to transition out
*/
onExiting: PropTypes.func,
/**
* Callback fired after the Overlay finishes transitioning out
*/
onExited: PropTypes.func,
/**
* Sets the direction of the Overlay.
*/
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
};
const defaultProps = {
animation: Fade,
rootClose: false,
show: false,
placement: 'right',
};
class Overlay extends React.Component {
render() {
const { animation, children, ...props } = this.props;
const transition = animation === true ? Fade : animation || null;
let child;
if (!transition) {
child = cloneElement(children, {
className: classNames(children.props.className, 'in'),
});
} else {
child = children;
}
return (
<BaseOverlay {...props} transition={transition}>
{child}
</BaseOverlay>
);
}
}
Overlay.propTypes = propTypes;
Overlay.defaultProps = defaultProps;
const connectedComponent = withTranslation()(Overlay);
export { connectedComponent as Overlay };
export default connectedComponent;
@@ -1,330 +0,0 @@
import contains from 'dom-helpers/query/contains';
import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import warning from 'warning';
import { Overlay } from './Overlay';
import createChainedFunction from './createChainedFunction';
/**
* Check if value one is inside or equal to the of value
*
* @param {string} one
* @param {string|array} of
* @returns {boolean}
*/
function isOneOf(one, of) {
if (Array.isArray(of)) {
return of.indexOf(one) >= 0;
}
return one === of;
}
const triggerType = PropTypes.oneOf(['click', 'hover', 'focus']);
const propTypes = {
/**
* Specify which action or actions trigger Overlay visibility
*/
trigger: PropTypes.oneOfType([triggerType, PropTypes.arrayOf(triggerType)]),
/**
* A millisecond delay amount to show and hide the Overlay once triggered
*/
delay: PropTypes.number,
/**
* A millisecond delay amount before showing the Overlay once triggered.
*/
delayShow: PropTypes.number,
/**
* A millisecond delay amount before hiding the Overlay once triggered.
*/
delayHide: PropTypes.number,
// FIXME: This should be `defaultShow`.
/**
* The initial visibility state of the Overlay. For more nuanced visibility
* control, consider using the Overlay component directly.
*/
defaultOverlayShown: PropTypes.bool,
/**
* An element or text to overlay next to the target.
*/
overlay: PropTypes.node.isRequired,
/**
* A function to be called once the hide is triggered
*/
handleHide: PropTypes.func,
/**
* @private
*/
onBlur: PropTypes.func,
/**
* @private
*/
onClick: PropTypes.func,
/**
* @private
*/
onFocus: PropTypes.func,
/**
* @private
*/
onMouseOut: PropTypes.func,
/**
* @private
*/
onMouseOver: PropTypes.func,
// Overridden props from `<Overlay>`.
/**
* @private
*/
target: PropTypes.oneOf([null]),
/**
* @private
*/
onHide: PropTypes.oneOf([null]),
/**
* @private
*/
show: PropTypes.oneOf([null]),
};
const defaultProps = {
defaultOverlayShown: false,
trigger: ['hover', 'focus'],
};
class OverlayTrigger extends React.Component {
constructor(props, context) {
super(props, context);
this.handleToggle = this.handleToggle.bind(this);
this.handleDelayedShow = this.handleDelayedShow.bind(this);
this.handleDelayedHide = this.handleDelayedHide.bind(this);
this.handleHide = createChainedFunction(
this.handleHide.bind(this),
props.handleHide
);
this.handleMouseOver = e =>
this.handleMouseOverOut(this.handleDelayedShow, e, 'fromElement');
this.handleMouseOut = e =>
this.handleMouseOverOut(this.handleDelayedHide, e, 'toElement');
this._mountNode = null;
this.state = {
show: props.defaultOverlayShown,
};
}
componentDidMount() {
this._mountNode = document.createElement('div');
this.renderOverlay();
}
componentDidUpdate() {
this.renderOverlay();
}
componentWillUnmount() {
ReactDOM.unmountComponentAtNode(this._mountNode);
this._mountNode = null;
clearTimeout(this._hoverShowDelay);
clearTimeout(this._hoverHideDelay);
}
handleDelayedHide() {
if (this._hoverShowDelay != null) {
clearTimeout(this._hoverShowDelay);
this._hoverShowDelay = null;
return;
}
if (!this.state.show || this._hoverHideDelay != null) {
return;
}
const delay =
this.props.delayHide != null ? this.props.delayHide : this.props.delay;
if (!delay) {
this.hide();
return;
}
this._hoverHideDelay = setTimeout(() => {
this._hoverHideDelay = null;
this.hide();
}, delay);
}
handleDelayedShow() {
if (this._hoverHideDelay != null) {
clearTimeout(this._hoverHideDelay);
this._hoverHideDelay = null;
return;
}
if (this.state.show || this._hoverShowDelay != null) {
return;
}
const delay =
this.props.delayShow != null ? this.props.delayShow : this.props.delay;
if (!delay) {
this.show();
return;
}
this._hoverShowDelay = setTimeout(() => {
this._hoverShowDelay = null;
this.show();
}, delay);
}
handleHide() {
this.hide();
}
// Simple implementation of mouseEnter and mouseLeave.
// React's built version is broken: https://github.com/facebook/react/issues/4251
// for cases when the trigger is disabled and mouseOut/Over can cause flicker
// moving from one child element to another.
handleMouseOverOut(handler, e, relatedNative) {
const target = e.currentTarget;
const related = e.relatedTarget || e.nativeEvent[relatedNative];
if ((!related || related !== target) && !contains(target, related)) {
handler(e);
}
}
handleToggle() {
if (this.state.show) {
this.hide();
} else {
this.show();
}
}
hide() {
this.setState({ show: false });
}
makeOverlay(overlay, props) {
return (
<Overlay
{...props}
show={this.state.show}
onHide={this.handleHide}
target={this}
>
{overlay}
</Overlay>
);
}
show() {
this.setState({ show: true });
}
renderOverlay() {
ReactDOM.unstable_renderSubtreeIntoContainer(
this,
this._overlay,
this._mountNode
);
}
render() {
const {
trigger,
overlay,
children,
onBlur,
onClick,
onFocus,
onMouseOut,
onMouseOver,
...props
} = this.props;
delete props.delay;
delete props.delayShow;
delete props.delayHide;
delete props.defaultOverlayShown;
const child = React.Children.only(children);
const childProps = child.props;
const triggerProps = {};
if (this.state.show) {
triggerProps['aria-describedby'] = overlay.props.id;
}
// FIXME: The logic here for passing through handlers on this component is
// inconsistent. We shouldn't be passing any of these props through.
triggerProps.onClick = createChainedFunction(childProps.onClick, onClick);
if (isOneOf('click', trigger)) {
triggerProps.onClick = createChainedFunction(
triggerProps.onClick,
this.handleToggle
);
}
if (isOneOf('hover', trigger)) {
warning(
!(trigger === 'hover'),
'[react-bootstrap] Specifying only the `"hover"` trigger limits the ' +
'visibility of the overlay to just mouse users. Consider also ' +
'including the `"focus"` trigger so that touch and keyboard only ' +
'users can see the overlay as well.'
);
triggerProps.onMouseOver = createChainedFunction(
childProps.onMouseOver,
onMouseOver,
this.handleMouseOver
);
triggerProps.onMouseOut = createChainedFunction(
childProps.onMouseOut,
onMouseOut,
this.handleMouseOut
);
}
if (isOneOf('focus', trigger)) {
triggerProps.onFocus = createChainedFunction(
childProps.onFocus,
onFocus,
this.handleDelayedShow
);
triggerProps.onBlur = createChainedFunction(
childProps.onBlur,
onBlur,
this.handleDelayedHide
);
}
this._overlay = this.makeOverlay(overlay, props);
return cloneElement(child, triggerProps);
}
}
OverlayTrigger.propTypes = propTypes;
OverlayTrigger.defaultProps = defaultProps;
export { OverlayTrigger };
@@ -1,84 +0,0 @@
---
name: Overlay Trigger
menu: Components
route: /components/overlay-trigger
---
import { Playground, Props } from 'docz'
import { Tooltip } from './../../tooltip'
import { OverlayTrigger } from './../index.js'
# Overlay Trigger
## Basic usage
### On Hover
<Playground>
<div style={{
display: 'flex',
justifyContent: 'space-between'
}}>
{
['right', 'left', 'bottom', 'top'].map((placement) => {
return (
<React.Fragment key={`example-${placement}`}>
<OverlayTrigger
key={`example-${placement}`}
placement={placement}
overlay={
<Tooltip
placement={placement}
className='in'
id={`tooltip-${placement}`}
>
Here I am!
</Tooltip>
}
>
<button> Tooltip on {placement}</button>
</OverlayTrigger>
</React.Fragment>
)
})
}
</div>
</Playground>
### On Click
<Playground>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
{['right', 'left', 'bottom', 'top'].map(placement => {
return (
<OverlayTrigger
key={`example-${placement}`}
placement={placement}
trigger="click"
overlay={
<Tooltip
placement={placement}
className="in"
id={`tooltip-${placement}`}
>
Here I am!
</Tooltip>
}
>
<button> Tooltip on {placement}</button>
</OverlayTrigger>
)
})}
</div>
</Playground>
## API
<Props of={OverlayTrigger} />
@@ -1,31 +0,0 @@
/**
* Safe chained function
*
* Will only create a new function if needed,
* otherwise will pass back existing functions or null.
*
* @param {function} functions to chain
* @returns {function|null}
*/
function createChainedFunction(...funcs) {
return funcs
.filter(f => f != null)
.reduce((acc, f) => {
if (typeof f !== 'function') {
throw new Error(
'Invalid Argument Type, must only provide functions, undefined, or null.'
);
}
if (acc === null) {
return f;
}
return function chainedFunction(...args) {
acc.apply(this, args);
f.apply(this, args);
};
}, null);
}
export default createChainedFunction;
@@ -1,2 +0,0 @@
export { Overlay } from './Overlay.js';
export { OverlayTrigger } from './OverlayTrigger.js';
@@ -1,209 +0,0 @@
.series-quick-switch
position: relative;
&:before
box-shadow: 0 0 0 5000px rgba(0, 0, 0, 0.8)
transition: opacity 0.3s ease
opacity: 0
content: ''
&:hover
z-index: 50
&:before
display: table;
opacity: 1
content: ''
&:not(.left):not(.right)
left: 50%
transform: translateX(-50%)
&.left
border-right: 1px solid var(--ui-border-color)
right: 0
&.right
left: 0
.series-switch
float: right
.study-switch
float: left
.study-browser
left: auto
right: 0
transform-origin: calc(100% - 1em) calc(1em + 17px)
.series-browser
left: 0
right: auto
transform-origin: 1em calc(1em + 15px)
.thumbnails-wrapper
transform-origin: 0% 0%
.thumbnail
float: left
.series-item
float: left
.title-label
color: var(--text-secondary-color)
font-size: 12px
font-weight: 500
line-height: 12px
opacity: 1
padding-bottom: 3px
text-align: center
transition: opacity 0.3s ease
.series-switch
padding-right: 10px;
.series-switch,
.study-switch
float: left
position: relative
.study-switch:hover:after
content: ''
display: block
height: 100px
left: 0
position: absolute
top: 0
width: 300px
.study-browser
left: 0
padding-top: 60px;
max-height: calc(100vh - 60px);
transform-origin: 1em calc(1em + 17px)
width: 300px
.study-browser-list
padding: 0 10px
.series-browser
max-height: 100vh
max-width: 720px
right: 0
transform-origin: calc(100% - 1em) calc(1em + 15px)
.thumbnails-wrapper
transition: transform 0.3s ease
transform-origin: 100% 0%
.thumbnail
float: right
.series-details
opacity: 0
transform: translateY(-100%)
transition: transform 0.3s ease, opacity 0.3s ease
.scrollable
padding-top: 15px
transition: padding-bottom 0.3s ease
.study-browser-series {
overflow hidden
}
.series-browser,
.study-browser
min-height: 120px
opacity: 0
position: absolute
top: 0
transform: scale(0)
transition: transform 0.3s ease , opacity 0.3s ease
z-index: 3
.series-box,
.study-box
height: 57px
width: 57px
.study-box
background-color: var(--ui-gray-dark)
border: solid 2px var(--ui-border-color-dark)
border-radius: 11px
.series-item
background-color: var(--box-background-color)
border-radius: 3px
float: right
height: 15px
margin: 2px
transition: opacity 0.3s ease
width: 15px
&.count
color: var(--text-primary-color)
background-color: transparent
font-size: 12px
font-weight: 500
line-height: 17px
text-align: center
&.active
background-color: var(--active-color)
&.series-triggered .series-browser,
.series-switch:hover .series-browser,
.study-switch:hover .study-browser
opacity: 1
transform: scale(1)
&.series-triggered .series-item,
.series-switch:hover .title-label
opacity: 0
&.series-triggered
.thumbnails-wrapper
transform: scale(0.9) translateY(80px)
.series-browser:not(:hover)
&>.scrollable
padding-bottom: 80px
&>.scrollNav
opacity: 0
.series-switch:hover .series-browser
.thumbnails-wrapper
transform: scale(1) translateY(0)
.thumbnail .series-details
opacity: 1
transform: translateY(0)
transition-delay: 0.3s
.study-browser-item.active .study-item-box
box-shadow: inset 0 0 0 3px var(--active-color)
@media screen and (max-width: 1599px)
.series-quick-switch
.series-box,
.study-box
width: 36px
height: 36px
.study-box
border-radius: 7px
.series-item
height: 10px
margin: 1px
width: 10px
&.count
font-size: 10px
font-weight: 300
line-height: 10px
Whitespace-only changes.
Whitespace-only changes.
@@ -1 +0,0 @@
export { QuickSwitch } from './QuickSwitch.js';
@@ -1,62 +0,0 @@
.ohif-radio-button-container {
--radio-button-dim: 20px;
}
.ohif-radio-button-container input {
position: absolute;
opacity: 0;
z-index: 10000;
cursor: pointer;
height: inherit;
width: inherit;
}
.ohif-radio-button-container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.ohif-radio-button {
width: var(--radio-button-dim);
height: var(--radio-button-dim);
position: absolute;
top: 25%;
left: 0;
background-color: var(--ui-gray-lighter);
border-radius: 50%;
z-index: 1;
}
.ohif-radio-button:hover {
background-color: var(--default-color);
border-radius: 100%;
}
.ohif-radio-button:after {
content: '';
position: absolute;
display: none;
width: 70%;
height: 70%;
}
.ohif-radio-button-container .ohif-radio-button.ohif-selected:after {
display: block;
top: 14%;
left: 14%;
border-radius: 50%;
background: white;
}
.ohif-radio-button-label {
font-size: 12px;
font-weight: 500;
}
@@ -1,74 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './RadioButtonList.css';
export class RadioButtonList extends Component {
static className = 'RadioButtonList';
//TODO: Add fields to propTypes.description?
//These would be label (required), id (required), and checked (optional).
static propTypes = {
description: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
checked: PropTypes.bool,
})
),
};
constructor(props) {
super(props);
this.state = {};
for (let button of props.description) {
if (button.checked) {
this.state.checked = button.id;
}
}
}
handleChange(e) {
this.setState({ checked: e.target.value });
}
render() {
let buttons = this.props.description.map(button => {
let input = (
<input
type="radio"
checked={this.state.checked === button.id}
onChange={e => {
this.handleChange(e);
}}
value={button.id}
/>
);
//needed to style the custom radio check
let inputSpan;
if (this.state.checked === button.id) {
inputSpan = (
<span className="ohif-radio-button ohif-selected">{input}</span>
);
} else {
inputSpan = <span className="ohif-radio-button">{input}</span>;
}
return (
<span className="ohif-radio-button-container" key={button.id}>
<label className="ohif-radio-button-label">
{inputSpan}
{button.label}
</label>
</span>
);
});
return (
<div className="ohif-radio-button-group">
<form>{buttons}</form>
</div>
);
}
}
@@ -1,74 +0,0 @@
---
name: Radio Button List
menu: Elements
route: /components/radio-button-list
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { RadioButtonList } from './../index'
import NameSpace from '../../../__docs__/NameSpace'
#Radio Button List
## Basic usage
<Playground>
<State
initial={{
description : [
{
'label' : 'Button 1',
'id' : 'button1',
'checked' : false
},
{
'label' : 'Button 2',
'id' : 'button2',
'checked' : false,
},
{
'label' : 'Button 3',
'id' : 'button3',
'checked' : true,
}
]
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
<div style={{ maxWidth: '300px', margin: '0 auto' }}>
<RadioButtonList description={[
{
'label' : 'Button 1',
'id' : 'button1',
'checked' : false
},
{
'label' : 'Button 2',
'id' : 'button2',
'checked' : false,
},
{
'label' : 'Button 3',
'id' : 'coolButton3',
'checked' : true,
}
]}/>
</div>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={RadioButtonList} />
## Translation Namespace
<NameSpace name="RadioButtonList" />
@@ -1 +0,0 @@
export { RadioButtonList } from './RadioButtonList.js';
@@ -1,84 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Icon } from './../../elements/Icon';
import './RoundedButtonGroup.css';
// TODO: Rename to Toggle?
class RoundedButtonGroup extends Component {
static className = 'RoundedButtonGroup';
static propTypes = {
options: PropTypes.arrayOf(
PropTypes.shape({
value: PropTypes.any,
label: PropTypes.string,
icon: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
name: PropTypes.string.isRequired,
}),
]),
})
),
value: PropTypes.string,
onValueChanged: PropTypes.func,
};
static defaultProps = {
options: [],
value: null,
};
onClickOption = value => {
let newValue = value;
if (this.props.value === value) {
newValue = null;
}
if (this.props.onValueChanged) {
this.props.onValueChanged(newValue);
}
};
render() {
let className = classnames(
RoundedButtonGroup.className,
'clearfix center-table'
);
const buttons = this.props.options.map((option, index) => {
const className = classnames({
roundedButtonWrapper: true,
noselect: true,
active: this.props.value === option.value,
});
const optionText = option.label && <span>{option.label}</span>;
const iconProps =
typeof option.icon === 'string' ? { name: option.icon } : option.icon;
const bottomLabel = option.bottomLabel && (
<div className="bottomLabel">{option.bottomLabel}</div>
);
return (
<div
key={index}
className={className}
onClick={() => this.onClickOption(option.value)}
>
<div className="roundedButton">
{optionText}
{iconProps && <Icon {...iconProps} />}
</div>
{bottomLabel}
</div>
);
});
return <div className={className}>{buttons}</div>;
}
}
export { RoundedButtonGroup };
@@ -1,51 +0,0 @@
---
name: Rounded Button Group
menu: Components
route: /components/rounded-button-group
---
import { Playground, Props } from 'docz';
import { State } from 'react-powerplug';
import { RoundedButtonGroup } from './../index.js';
# Rounded Button Group
A basic styled toggle switch
## Basic usage
<Playground>
<State
initial={{
value: null,
options: [
{
value: 'studies',
icon: 'th',
bottomLabel: 'Series',
},
{
value: 'measurements',
icon: 'th-list',
bottomLabel: 'Measurements',
},
],
}}
>
{({ state, setState }) => (
<React.Fragment>
<div>
<pre>value: {JSON.stringify(state.value, null, 2)}</pre>
</div>
<RoundedButtonGroup
{...state}
onValueChanged={value => setState({ value })}
/>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={RoundedButtonGroup} />
@@ -1 +0,0 @@
export { RoundedButtonGroup } from './RoundedButtonGroup.js';
@@ -1,37 +0,0 @@
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
export default class InputRadio extends Component {
static propTypes = {
value: PropTypes.string,
label: PropTypes.string.isRequired,
itemData: PropTypes.object.isRequired,
labelClass: PropTypes.string,
id: PropTypes.string.isRequired,
onSelected: PropTypes.func.isRequired,
};
render() {
const labelClass = this.props.labelClass ? this.props.labelClass : '';
return (
<label
className={'wrapperLabel radioLabel ' + labelClass}
htmlFor={this.props.id}
>
<input
type="radio"
id={this.props.id}
className="radioInput"
value={this.props.value}
onChange={this.onSelected}
/>
<span className="wrapperText">{this.props.label}</span>
</label>
);
}
onSelected = evt => {
this.props.onSelected(evt, this.props.itemData);
};
}
@@ -1,192 +0,0 @@
import './SelectTree.styl';
import React, { Component } from 'react';
import { Icon } from './../../elements/Icon';
import InputRadio from './InputRadio.js';
import PropTypes from 'prop-types';
import SelectTreeBreadcrumb from './SelectTreeBreadcrumb.js';
import cloneDeep from 'lodash.clonedeep';
export class SelectTree extends Component {
static propTypes = {
autoFocus: PropTypes.bool,
searchEnabled: PropTypes.bool,
selectTreeFirstTitle: PropTypes.string,
selectTreeSecondTitle: PropTypes.string,
/** Called when 'componentDidUpdate' is triggered */
onComponentChange: PropTypes.func,
/** [{ label, value, items[]}] - An array of items than can be expanded to show child items */
items: PropTypes.array.isRequired,
/** fn(evt, item) - Called when a child item is selected; receives event and selected item */
onSelected: PropTypes.func.isRequired,
};
static defaultProps = {
searchEnabled: true,
autoFocus: true,
selectTreeFirstTitle: 'First Level itens',
items: [],
};
constructor(props) {
super(props);
this.state = {
searchTerm: null,
currentNode: null,
value: null,
};
}
render() {
const treeItems = this.getTreeItems();
return (
<div className="selectTree selectTreeRoot">
<div className="treeContent">
{this.headerItem()}
<div className="treeOptions">
{this.state.currentNode && (
<SelectTreeBreadcrumb
onSelected={this.onBreadcrumbSelected}
label={this.state.currentNode.label}
value={this.state.currentNode.value}
/>
)}
<div className="treeInputsWrapper">
<div className="treeInputs">{treeItems}</div>
</div>
</div>
</div>
</div>
);
}
componentDidUpdate = () => {
if (this.props.onComponentChange) {
this.props.onComponentChange();
}
};
isLeafSelected = item => item && !Array.isArray(item.items);
getLabelClass = item => {
let labelClass = 'treeLeaf';
if (this.state.searchTerm || Array.isArray(item.items)) {
labelClass = 'treeNode';
}
return labelClass;
};
filterItems() {
const filteredItems = [];
const rawItems = cloneDeep(this.props.items);
rawItems.forEach(item => {
if (Array.isArray(item.items)) {
item.items.forEach(item => {
const label = item.label.toLowerCase();
const searchTerm = this.state.searchTerm.toLowerCase();
if (label.indexOf(searchTerm) !== -1) {
filteredItems.push(item);
}
});
} else {
const label = item.label.toLowerCase();
const searchTerm = this.state.searchTerm.toLowerCase();
if (label.indexOf(searchTerm) !== -1) {
filteredItems.push(item);
}
}
});
return filteredItems;
}
getTreeItems() {
const storageKey = 'SelectTree';
let treeItems;
if (this.state.searchTerm) {
treeItems = this.filterItems();
} else if (this.state.currentNode) {
treeItems = cloneDeep(this.state.currentNode.items);
} else {
treeItems = cloneDeep(this.props.items);
}
return treeItems.map((item, index) => {
let itemKey = index;
if (this.state.currentNode) {
itemKey += `_${this.state.currentNode.value}`;
}
return (
<InputRadio
key={itemKey}
id={`${storageKey}_${item.value}`}
name={index}
itemData={item}
value={item.value}
label={item.label}
labelClass={this.getLabelClass(item)}
onSelected={this.onSelected}
/>
);
});
}
headerItem = () => {
let title = this.props.selectTreeFirstTitle;
if (this.state.currentNode && this.props.selectTreeSecondTitle) {
title = this.props.selectTreeSecondTitle;
}
return (
<div className="wrapperLabel treeHeader">
<div className="wrapperText">{title}</div>
{this.props.searchEnabled && (
<div className="wrapperSearch">
<div className="searchIcon">
<Icon name="search" />
</div>
<input
type="text"
className="searchInput"
placeholder="Search labels"
autoFocus={this.props.autoFocus}
onChange={this.searchLocations}
value={this.state.searchTerm ? this.state.searchTerm : ''}
/>
</div>
)}
</div>
);
};
searchLocations = evt => {
this.setState({
currentNode: null,
searchTerm: evt.currentTarget.value,
});
};
onSelected = (event, item) => {
if (this.isLeafSelected(item)) {
this.setState({
searchTerm: null,
currentNode: null,
value: null,
});
} else {
this.setState({
currentNode: item,
});
}
return this.props.onSelected(event, item);
};
onBreadcrumbSelected = () => {
this.setState({
currentNode: null,
});
};
}
@@ -1,108 +0,0 @@
@import './../../design/styles/common/global.styl'
.selectTreeRoot
text-align: initial;
width: 320px;
max-height: inherit;
font-size: 14px;
line-height: 26px;
.treeContent
max-height: inherit;
overflow: hidden;
border: 1px solid #c4c4c4;
border-radius: 5px;
background-color: #ffffff;
display: flex;
flex-direction: column;
.treeHeader
background-color: #c0c0c0;
border-bottom: 1px solid #c4c4c4;
color: #303030;
padding: 5px;
text-align: center;
.wrapperText
font-weight: bold;
padding-bottom: 2px;
.wrapperSearch
width: 100%;
.searchIcon
position: absolute;
width: 18px;
height: 15px;
margin: 7px 10px 13px 10px;
color: #a3a3a3;
.searchInput
width: 100%;
padding: 6px 12px 6px 35px;
background-color: #fff;
background-image: none;
border: 1px solid #9b9b9b;
border-radius: 4px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
&:focus
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 8px rgba(102, 175, 233, 0.6);
.wrapperText
white-space: nowrap;
cursor: pointer;
.selectTreeBreadcrumb
.wrapperLabel
cursor: pointer;
padding: 0 12px;
height: 41px;
width: 100%;
line-height: 41px;
.backIcon
padding-right: 10px;
svg
width: 14px;
height: 14px;
position: relative;
top: 2px;
color: #457fb0;
.treeOptions
overflow: auto;
height: 100%;
.selectTreeBreadcrumb + .treeInputsWrapper .treeInputs .treeLeaf
border: none;
height: initial;
line-height: 26px;
& > .treeInputsWrapper > .treeInputs .treeLeaf
border-bottom: 1px solid #ccc;
height: 41px;
line-height: 41px;
.treeNode, .treeLeaf
cursor: pointer;
display: block;
overflow: hidden;
font-weight: normal;
box-shadow: 0 0 0 200px transparent;
padding: 0 12px;
.treeNode
border-bottom: 1px solid #ccc;
height: 41px;
line-height: 41px;
.radioLabel .radioInput
display: none;
@@ -1,37 +0,0 @@
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Icon } from './../../elements/Icon';
export default class SelectTreeBreadcrumb extends Component {
static propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
onSelected: PropTypes.func.isRequired,
};
render() {
return (
<div className="selectTreeBreadcrumb">
<label
className="wrapperLabel radioLabel"
htmlFor="selectTreeBreadcrumb"
>
<input
type="radio"
id="selectTreeBreadcrumb"
className="treeNode radioInput"
value={this.props.value}
onChange={this.props.onSelected}
/>
<span className="wrapperText">
<span className="backIcon">
<Icon name="fast-backward" />
</span>
{this.props.label}
</span>
</label>
</div>
);
}
}
@@ -1,42 +0,0 @@
---
name: Select Tree
menu: Components
route: /components/select-tree
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { SelectTree } from './../index.js'
import selectTreeItems from './selectTreeItems.js'
# SelectTree
## Basic usage
<Playground>
<State initial={{
items: selectTreeItems,
}}>
{({ state, setState }) => (
<React.Fragment>
<pre style={{ maxHeight: '135px', overflowX: 'auto' }}>
{JSON.stringify(state.items, null, 2)}
</pre>
<SelectTree
items={state.items}
onSelected={(event, item) => setState({ itemLabelSelected: item.label }) }
selectTreeFirstTitle="Select Tree Example Header"
autoFocus={false}
/>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={SelectTree} />
@@ -1,30 +0,0 @@
export default [
{
label: 'A',
value: 'A',
items: [
{
label: 'Abdominal Wall',
value: 'AbdominalWall',
},
{
label: 'Adrenal Left',
value: 'AdrenalLeft',
},
],
},
{
label: 'B',
value: 'B',
items: [
{
label: 'Brain',
value: 'Brain',
},
{
label: 'Breast',
value: 'Breast',
},
],
},
];
@@ -1 +0,0 @@
export { SelectTree } from './SelectTree';
@@ -1,95 +0,0 @@
import React, { Component, useState } from 'react';
import PropTypes from 'prop-types';
import { TextInput } from '@ohif/ui';
import './SimpleDialog.styl';
class SimpleDialog extends Component {
static propTypes = {
children: PropTypes.node,
componentRef: PropTypes.any,
componentStyle: PropTypes.object,
rootClass: PropTypes.string,
isOpen: PropTypes.bool,
headerTitle: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
};
static defaultProps = {
isOpen: true,
componentStyle: {},
rootClass: '',
};
static InputDialog = ({ onSubmit, defaultValue, title, label, onClose }) => {
const [value, setValue] = useState(defaultValue);
const onSubmitHandler = () => {
onSubmit(value);
};
return (
<div className="InputDialog">
<SimpleDialog
headerTitle={title}
onClose={onClose}
onConfirm={onSubmitHandler}
>
<TextInput
type="text"
value={value}
onChange={event => setValue(event.target.value)}
label={label}
/>
</SimpleDialog>
</div>
);
};
render() {
return (
<React.Fragment>
{this.props.isOpen && (
<div
className={`simpleDialog ${this.props.rootClass} `}
ref={this.props.componentRef}
style={this.props.componentStyle}
>
<form>
<div className="header">
<span className="closeBtn" onClick={this.onClose}>
<span className="closeIcon">x</span>
</span>
<h4 className="title">{this.props.headerTitle}</h4>
</div>
<div className="content">{this.props.children}</div>
<div className="footer">
<button className="btn btn-default" onClick={this.onClose}>
Cancel
</button>
<button className="btn btn-primary" onClick={this.onConfirm}>
Confirm
</button>
</div>
</form>
</div>
)}
</React.Fragment>
);
}
onClose = event => {
event.preventDefault();
event.stopPropagation();
this.props.onClose();
};
onConfirm = event => {
event.preventDefault();
event.stopPropagation();
this.props.onConfirm();
};
}
export { SimpleDialog };
@@ -1,110 +0,0 @@
@import './../../design/styles/common/button.styl'
@import './../../design/styles/common/global.styl'
.InputDialog
.simpleDialog
position: relative
.simpleDialog
position: relative;
z-index: 1000;
border: 0;
border-radius: 6px;
background-color: var(--ui-gray-darker);
.header
border-bottom: 3px solid var(--primary-background-color);
padding: 19px 22px 17px;
position: relative;
.title
font-size: 20px;
font-weight: 600;
line-height: 24px;
padding-right: 40px;
color: var(--text-secondary-color);
margin: 0;
.closeBtn
-webkit-transform: translateY(-50%);
color: var(--text-secondary-color);
height: 20px;
opacity: 1;
overflow: hidden;
padding: 2px;
position: absolute;
right: 21px;
text-align: center;
text-shadow: none;
top: 50%;
transform: translateY(-50%);
transition: color 0.3s ease;
width: 20px;
.closeIcon
color: transparent;
display: block;
font-size: 0;
height: 100%;
line-height: 0;
overflow: hidden;
position: relative;
width: 100%;
&:after, &:before
content: ' ';
display: block;
height: 2px;
transition: background-color 0.3s ease;
width: 19px;
background-color: var(--text-secondary-color);
&:before
left: 1px;
position: absolute;
top: 1px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-transform-origin: 1px 50%;
transform-origin: 1px 50%;
&:after
right: 1px;
position: absolute;
top: 1px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: calc(100% - 1px) 50%;
transform-origin: calc(100% - 1px) 50%;
.content
padding: 16px 22px 25px;
position: relative;
color: var(--text-primary-color);
.simpleDialogSelect, .simpleDialogInput
background-color: var(--input-background-color);
color: var(--text-primary-color);
border: 0;
border-radius: 2px;
font-size: 14px;
height: 30px;
width: 100%;
line-height: 16px;
padding: 8px 9px 6px;
margin-top: 10px;
display: block;
.simpleDialogLabelFor
font-size: 14px;
font-weight: 700;
line-height: 16px;
margin: 10px 0 0 0;
color: var(--text-primary-color);
.footer
padding: 15px;
text-align: right;
.btn
margin-left: 5px;
@@ -1,83 +0,0 @@
---
name: Simple Dialog
menu: Components
route: /components/simple-dialog
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { SimpleDialog } from './../index.js'
# Simple Dialog
Its similar to a modal but we can pass its style as props and manipulate it as
we want. Initially used to display tool data changes in our Viewport.
## Basic usage
<Playground>
<State initial={{
isDialogOpen: false,
inputValue: '',
selectValue: '',
dialogStyle: {
top: '50px',
left: '100px',
position: 'absolute'
}
}}>
{({ state, setState }) => (
<React.Fragment>
<div><pre>{JSON.stringify(state, null, 2) }</pre></div>
{/* Toggle Dialog */}
<button onClick={() => setState({ isDialogOpen: !state.isDialogOpen})}>
{state.isDialogOpen ? 'Close Dialog' : 'Open Dialog'}
</button>
<SimpleDialog
isOpen={state.isDialogOpen}
headerTitle='Example Dialog Header'
onClose={() => setState({ isDialogOpen: false })}
onConfirm={() => setState({
isDialogOpen: false,
// If you don't want to update external state until complete,
// This is where you could grab current field values and update parent
// With setState
//
// inputValue: this.input.value,
// selectValue: this.select.value
})}
componentStyle={state.dialogStyle}
onValueChanged={(value) => setState({value}) }
>
<label htmlFor='input' className='simpleDialogLabelFor'>Input Example</label>
<input
id='input'
type='text'
className='simpleDialogInput'
autoComplete='off'
onChange={(e) => setState({inputValue: e.target.value})}
/>
<label htmlFor='select' className='simpleDialogLabelFor'>SelectExample</label>
<select
name='select'
id='select'
className='simpleDialogSelect'
onChange={(e) => setState({selectValue: e.target.value})}
>
<option value='Option1'>Option1</option>
<option value='Option2'>Option2</option>
<option value='Option3'>Option3</option>
<option value='Option4'>Option4</option>
</select>
</SimpleDialog>
</React.Fragment>
)}
</State>
</Playground>
## API
<Props of={SimpleDialog} />
@@ -1 +0,0 @@
export { SimpleDialog } from './SimpleDialog.js';
@@ -1,53 +0,0 @@
import React from 'react';
import SnackbarItem from './SnackbarItem';
import './Snackbar.css';
import { useSnackbarContext } from '../../contextProviders';
const SnackbarContainer = () => {
const { snackbarItems, hide } = useSnackbarContext();
const renderItem = item => {
return <SnackbarItem key={item.itemId} options={item} onClose={hide} />;
};
if (!snackbarItems) {
return null;
}
const renderItems = () => {
const items = {
topLeft: [],
topCenter: [],
topRight: [],
bottomLeft: [],
bottomCenter: [],
bottomRight: [],
};
snackbarItems.map(item => {
items[item.position].push(item);
});
return (
<div>
{Object.keys(items).map(pos => {
if (!items[pos].length) {
return null;
}
return (
<div key={pos} className={`sb-container sb-${pos}`}>
{items[pos].map((item, index) => (
<div key={item.id + index}>{renderItem(item)}</div>
))}
</div>
);
})}
</div>
);
};
return <>{renderItems()}</>;
};
export default SnackbarContainer;
@@ -1,31 +0,0 @@
import React, { useState, useEffect } from 'react';
const SnackbarItem = ({ options, onClose }) => {
const handleClose = () => {
onClose(options.id);
};
useEffect(() => {
if (options.autoClose) {
setTimeout(() => {
handleClose();
}, options.duration);
}
}, []);
return (
<div
className={`${options.visible ? '' : 'sb-hidden'} sb-${
options.type
} sb-item`}
>
<span className="sb-closeBtn" onClick={handleClose}>
<span className="sb-closeIcon">x</span>
</span>
{options.title && <div className="sb-title">{options.title}</div>}
{options.message && <div className="sb-message">{options.message}</div>}
</div>
);
};
export default SnackbarItem;
@@ -1,6 +0,0 @@
export default {
INFO: 'info',
WARNING: 'warning',
SUCCESS: 'success',
ERROR: 'error',
};
@@ -1,99 +0,0 @@
:root {
--text-secondary-color: #91B9CD;
--active-color: #20A5D6;
--primary-background-color: black;
--ui-border-color-dark: #3C5D80
--sidebar-transition: all 0.3s ease;
}
.ThumbnailEntry.active .ImageThumbnail
border-color: var(--active-color);
box-shadow: none
transition: var(--sidebar-transition);
.ImageThumbnail
background-color: var(--primary-background-color);
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
border: 5px solid transparent
border-radius: 12px
height: 135px
margin: 0 auto
padding 5px
position: relative
transition: var(--sidebar-transition);
width: 217px
-moz-background-clip: padding
-webkit-background-clip: padding
background-clip: padding-box
// Thumbnail Loading Indicator
.image-thumbnail-loading-indicator
position: absolute
left: 50%
top: 50%
margin: -22px 0 0 -22px
display: block
width: 44px
height: 44px
&:after
content: " "
display: block
width: 32px
height: 32px
margin: 1px
border-radius: 50%
border: 5px solid var(--ui-border-color-dark)
border-color: var(--ui-border-color-dark) transparent var(--ui-border-color-dark) transparent
animation: image-thumbnail-loading-indicator 1.2s linear infinite
@keyframes image-thumbnail-loading-indicator
0%
transform: rotate(0deg)
100%
transform: rotate(360deg)
// The End...
.image-thumbnail-canvas
height: 100%
overflow: hidden
display: flex
justify-content: center
img
-webkit-user-drag: none
pointer-events: none
.thumbnailLoadingIndicator
display: none
pointer-events: none
color: var(--text-secondary-color);
height: 20px
width: 100%
top: 0
left: 0
right: 0
bottom: 0
margin: auto
position: absolute
&.d-block
display: block;
p
text-align: center
font-size: 10pt
.image-thumbnail-progress-bar
position: relative
width: 100%
height: 3px
top: -5px
.image-thumbnail-progress-bar-inner
height: 100%
width: 0
border-radius: 5px
background-color: var(--active-color);
@@ -1,40 +0,0 @@
.study-browser
float: left
height: 100%
width: 100%
overflow: hidden
background-color: black
padding-bottom: 20px
padding-top: 10px
.scrollable-study-thumbnails
height: 100%
overflow-y: auto
overflow-x: hidden
padding-bottom: 50px
padding-right: 16px
padding-left: 4px
margin-right: -16px
display: flex;
flex-direction: column;
&::-webkit-scrollbar
display: none
.thumbnail-container
margin: 0 auto;
padding-bottom: 1.5rem;
.noselect
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer */
-khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */
-webkit-user-select: none; /* Chrome, Safari, and Opera */
-webkit-touch-callout: none; /* Disable Android and iOS callouts*/
.draggable
cursor: copy
cursor: -webkit-grab
cursor: -moz-grab
@@ -1,98 +0,0 @@
@import './../../design/styles/common/global.styl'
:root {
--series-count-background-color: #678696;
}
.thumbnail
cursor: pointer
display: table
margin: 0 auto;
min-width: 90%;
padding-bottom: 1.5rem;
.alt-image-text
align-items: center;
justify-content: center;
background-color: var(--primary-background-color);
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
border: 5px solid transparent;
border-radius: 12px;
height: 135px;
margin: 0 auto;
padding: 5px;
position: relative;
transition: var(--sidebar-transition);
width: 217px;
display: flex;
h1
text-align: center
color: var(--text-primary-color);
.series-details
display: flex;
flex-direction: column
color: var(--text-primary-color);
font-size: 14px
line-height: 1.3em
margin-top: 5px
margin-left: 1rem
margin-right: 0.5rem
max-width: 217px
position: relative
word-wrap: break-word
.series-description
flex-grow: 1
&.info-only .series-description
display: none
.series-information
display: flex
flex-direction: row
justify-content: space-between
.item-frames .icon
height: 18px
.item-series.image-frames .icon
background-color: var(--series-count-background-color);
margin-top: 6px
position: relative
&:after
content: ''
background-color: var(--active-color);
box-shadow: 1px 1px rgba(0, 0, 0, .115)
left: -4px
position: absolute
top: -4px
&
&:after
content: ''
border: 0px solid var(--series-count-background-color)
display: inline-block
height: 11px
width: 11px
.value
color: var(--text-secondary-color);
display: inline-block
font-size: 12px
margin-left: 4px
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
width: calc(100% - 15px)
vertical-align: middle
.icon
color: var(--active-color);
display: inline-block
font-size: 10px
font-weight: 900
text-align: right
width: 11px
@@ -1,30 +0,0 @@
.ExampleDropTarget {
display: flex;
justify-content: center;
flex-direction: column;
min-height: 100px;
border: 2px dashed black;
padding: 20px;
transition: 0.2s all ease;
}
.ExampleDropTarget h4,
.ExampleDropTarger p {
text-align: center;
word-break: break-word;
}
.ExampleDropTarget.can-drop {
border: 2px dashed #20a5d6;
background-color: #fafafa;
color: black;
}
.ExampleDropTarget.hovered {
border: 2px dashed #20a5d6;
background-color: black;
color: #91b9cd;
cursor: copy;
}
@@ -1,22 +0,0 @@
---
name: Study Browser
menu: Components
route: /components/study-browser
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { WrappedStudyBrowser } from './wrappedStudyBrowser.js'
import { StudyBrowser } from './../index.js'
# Study Browser
## Basic usage
<Playground>
<WrappedStudyBrowser />
</Playground>
## API
<Props of={StudyBrowser} />
@@ -1,41 +0,0 @@
import React, { Component } from 'react';
import { DragDropContext } from 'react-dnd';
import TouchBackend from 'react-dnd-touch-backend';
//
import {
studies,
onThumbnailClick,
onThumbnailDoubleClick,
} from './exampleStudies.js';
import ExampleDropTarget from './ExampleDropTarget.js';
import { StudyBrowser } from './../index.js';
class StudyBrowserContainer extends Component {
render() {
//const viewportData = [null, null, null, null];
return (
<React.Fragment>
<ExampleDropTarget />
<StudyBrowser
studies={studies}
onThumbnailClick={onThumbnailClick}
onThumbnailDoubleClick={onThumbnailDoubleClick}
/>
</React.Fragment>
);
}
}
// Note:
// Normally, the top level APP component is wrapped with the DragDropContext
// We wrap this component to create a simple/local example.
const WrappedStudyBrowser = DragDropContext(
TouchBackend({ enableMouseEvents: true }),
null,
true
)(StudyBrowserContainer);
// http://react-dnd.github.io/react-dnd/docs/api/drag-drop-context
export { WrappedStudyBrowser };
@@ -1,2 +0,0 @@
export { StudyBrowser } from './StudyBrowser.js';
export { Thumbnail } from './Thumbnail.js';
@@ -1,119 +0,0 @@
/* NOTE: the order of these styles DO matter */
/* Will edit everything selected including everything between a range of dates */
.CalendarDay__selected_span {
background: var(--datePicker-picker-background);
color: var(--datePicker-picker-text);
border-color: #e4e7e7;
}
/* Will edit selected date or the endpoints of a range of dates */
.CalendarDay__selected {
background: var(--datePicker-picker-background);
color: var(--datePicker-picker-text);
border-color: #e4e7e7;
}
/* Will edit when hovered over. _span style also has this property */
.CalendarDay__selected:hover,
.CalendarDay__default:hover {
background: var(--datePicker-picker-background);
color: var(--datePicker-picker-text);
border-color: #e4e7e7;
}
/* Will edit when the second date (end date) in a range of dates
is not yet selected. Edits the dates between your mouse and said date */
.CalendarDay__hovered_span:hover,
.CalendarDay__hovered_span {
background: var(--datePicker-picker-background);
color: var(--datePicker-picker-text);
border-color: #e4e7e7;
}
/* EXTERIOR INPUT STYLE */
/* Container - placement */
.DateRangePicker {
height: 40px;
margin: 0 5px 20px 5px;
cursor: pointer;
border: none;
width: 100%;
}
/* Container - visual */
.DateRangePickerInput {
width: calc(100% - 10px); /* Just use padding? */
background-color: var(--datePicker-input-background);
border-color: var(--datePicker-input-background);
color: var(--datePicker-picker-text-input);
height: 40px;
}
.DateRangePickerInput.DateRangePickerInput__withBorder {
border-radius: 4px;
background-color: var(--datePicker-input-background);
}
/* Input Container */
.DateInput {
width: 97px;
height: 38px;
border-radius: 4px;
background-color: var(--datePicker-input-background);
}
/* Actual Input Element */
.DateInput > .DateInput_input {
border-color: transparent;
background-color: transparent;
color: var(--datePicker-picker-text-input);
height: 38px;
font-size: 10pt;
padding: 0;
}
/* PRESETS */
.PresetDateRangePicker_panel {
display: flex;
justify-content: space-between;
}
.PresetDateRangePicker_button {
margin: 0;
padding: 4px 8px;
color: #00a699;
background: var(--datePicker-picker-text-input);
}
.PresetDateRangePicker_button:hover {
background: #00a699;
color: var(--datePicker-picker-text-input);
}
.CalendarDay__blocked_out_of_range:hover {
background: #fff;
cursor: not-allowed;
color: var(--datePicker-picker-text-disabled);
}
.DateRangePicker_select {
background-color: white;
color: var(--datePicker-picker-text);
border: 1px solid #e4e7e7;
border-radius: 4px;
display: inline-block;
padding: 3px 20px 3px 10px;
font-size: 14px;
cursor: pointer;
appearance: none;
background-image: linear-gradient(45deg, transparent 50%, gray 50%),
linear-gradient(135deg, gray 50%, transparent 50%);
background-position: calc(100% - 11px) 11px, calc(100% - 6px) calc(11px);
background-size: 5px 5px, 5px 5px;
background-repeat: no-repeat;
}
.DateRangePicker_select:hover {
border-color: #c4c4c4;
}
@@ -1,140 +0,0 @@
// If you want to continue using CSS stylesheets and classes...
// https://github.com/airbnb/react-dates#initialize
import 'react-dates/initialize';
import 'react-dates/lib/css/_datepicker.css';
import './CustomDateRangePicker.css';
import React from 'react';
import PropTypes from 'prop-types';
import { DateRangePicker } from 'react-dates';
import moment from 'moment';
import i18n from '@ohif/i18n';
import { useTranslation } from 'react-i18next';
function CustomDateRangePicker(props) {
moment.locale(i18n.language); // using i18n in the date picker
const { t } = useTranslation('DatePicker');
const {
onDatesChange,
startDate,
endDate,
presets,
...dateRangePickerProps
} = props;
const renderDatePresets = () => {
const { presets } = props;
return (
<div className="PresetDateRangePicker_panel">
{presets.map(({ text, start, end }) => {
const isSelected = startDate === start && endDate === end;
return (
<button
key={text}
type="button"
className={`PresetDateRangePicker_button ${
isSelected ? 'PresetDateRangePicker_button__selected' : ''
}`}
onClick={() =>
onDatesChange({
startDate: start,
endDate: end,
preset: true,
})
}
>
{text}
</button>
);
})}
</div>
);
};
const renderMonthElement = ({ month, onMonthSelect, onYearSelect }) => {
const containerStyle = {
margin: '0 5px',
};
const renderYearsOptions = () => {
const yearsRange = 20;
const options = [];
for (let i = 0; i < yearsRange; i++) {
const year = moment().year() - i;
options.push(<option value={year}>{year}</option>);
}
return options;
};
renderMonthElement.propTypes = {
onMonthSelect: PropTypes.func,
onYearSelect: PropTypes.func,
};
return (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div style={containerStyle}>
<select
className="DateRangePicker_select"
value={month.month()}
onChange={e => onMonthSelect(month, e.target.value)}
>
{moment.months().map((label, value) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</div>
<div style={containerStyle}>
{}
<select
className="DateRangePicker_select"
value={month.year()}
onChange={e => onYearSelect(month, e.target.value)}
>
{renderYearsOptions()}
</select>
</div>
</div>
);
};
return (
<DateRangePicker
{...dateRangePickerProps}
startDate={startDate}
endDate={endDate}
renderCalendarInfo={renderDatePresets}
onDatesChange={onDatesChange}
renderMonthElement={renderMonthElement}
startDatePlaceholderText={t('Start Date')}
endDatePlaceholderText={t('End Date')}
phrases={{
closeDatePicker: t('Common:Close'),
clearDates: t('Clear dates'),
}}
/>
);
}
CustomDateRangePicker.propTypes = {
presets: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
start: PropTypes.required,
end: PropTypes.required,
})
),
onDatesChange: PropTypes.func.isRequired,
startDate: PropTypes.instanceOf(Date),
endDate: PropTypes.instanceOf(Date),
month: PropTypes.instanceOf(Date),
};
export default CustomDateRangePicker;
@@ -1,38 +0,0 @@
import React, { PureComponent } from 'react';
import { Icon } from './../../elements/Icon';
import PropTypes from 'prop-types';
class PageToolbar extends PureComponent {
static propTypes = {
onImport: PropTypes.func,
};
onImport = event => {
if (this.props.onImport) {
this.props.onImport(event);
}
};
getImportTool() {
if (this.props.onImport) {
return (
<div className="addNewStudy btn-file">
<label
htmlFor="btnImport"
style={{ width: '18px' }}
onClick={this.onImport}
>
<Icon name="plus" />
</label>
</div>
);
}
}
render() {
return <div className="studylistToolbar">{this.getImportTool()}</div>;
}
}
export { PageToolbar };
@@ -1,63 +0,0 @@
.pagination-area
display: flex;
color: var(--text-secondary-color);
font-size: 13px
font-weight: normal !important
label
font-weight: normal
select
margin: 5px
background-color: var(--primary-background-color)
color: white
.rows-dropdown
width: 25%;
padding-right: 15px;
padding-left: 15px;
.pagination-buttons
width: 75%;
padding-right: 15px;
padding-left: 15px;
.form-group
margin-bottom: 15px;
.rows-per-page label.wrapperLabel
display: inline-table !important
margin: 0 4px
select
margin: 0px 4px 0px 4px
width: 42px
.page-buttons
margin: 0
text-align: right
font-weight: normal
ul.pagination-control
margin: 0
li
display: table-cell
padding: 5px 2px
button
padding: 4px 8px
background-color: var(--primary-background-color)
border-color: var(--ui-gray)
color: var(--ui-gray-darkest)
color: white
text-decoration: none
&:hover:enabled
color: var(--active-color)
.active
button
background-color: var(--ui-gray)
border-color: #ddd
color: white
@@ -1,18 +0,0 @@
import React from 'react';
import { Icon } from './../../elements/Icon';
// TODO: useTranslation
import { withTranslation } from '../../contextProviders';
function StudyListLoadingText({ t: translate }) {
return (
<div className="loading-text">
{translate('Loading')}... <Icon name="circle-notch" animation="pulse" />
</div>
);
}
const connectedComponent = withTranslation('StudyListLoadingText')(
StudyListLoadingText
);
export { connectedComponent as StudyListLoadingText };
@@ -1,150 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { isInclusivelyBeforeDay } from 'react-dates';
import CustomDateRangePicker from './CustomDateRangePicker.js';
import { Icon } from './../../elements/Icon';
import { useTranslation } from 'react-i18next';
const getDateEntry = (datePicked, rangeDatePicked) => {
return rangeDatePicked || datePicked || null;
};
const getDateEntryFromRange = (today, numOfDays, edge = 'start') => {
if (typeof numOfDays !== 'number') {
return;
}
if (edge === 'end') {
return today;
} else {
today.subtract(numOfDays, 'days');
}
};
function TableSearchFilter(props) {
const {
meta,
values,
onSort,
onValueChange,
sortFieldName,
sortDirection,
// TODO: Rename
studyListDateFilterNumDays,
} = props;
const { studyDateTo, studyDateFrom } = values || {};
const [focusedInput, setFocusedInput] = useState(null);
const { t, ready: translationsAreReady } = useTranslation('Common');
const sortIcons = ['sort', 'sort-up', 'sort-down'];
const sortIconForSortField =
sortDirection === 'asc' ? sortIcons[1] : sortIcons[2];
const today = moment();
const lastWeek = moment().subtract(7, 'day');
const lastMonth = moment().subtract(1, 'month');
const defaultStartDate = getDateEntryFromRange(
today,
studyListDateFilterNumDays,
'start'
);
const defaultEndDate = getDateEntryFromRange(
today,
studyListDateFilterNumDays,
'end'
);
const studyDatePresets = [
{
text: t('Today'),
start: today,
end: today,
},
{
text: t('Last 7 days'),
start: lastWeek,
end: today,
},
{
text: t('Last 30 days'),
start: lastMonth,
end: today,
},
];
return translationsAreReady
? meta.map((field, i) => {
const { displayText, fieldName, inputType } = field;
const isSortField = sortFieldName === fieldName;
const sortIcon = isSortField ? sortIconForSortField : sortIcons[0];
return (
<th key={`${fieldName}-${i}`}>
<label
htmlFor={`filter-${fieldName}`}
onClick={() => onSort(fieldName)}
>
{`${displayText}`}
<Icon name={sortIcon} style={{ fontSize: '12px' }} />
</label>
{inputType === 'text' && (
<input
type="text"
id={`filter-${fieldName}`}
className="form-control studylist-search"
value={values[fieldName]}
onChange={e => onValueChange(fieldName, e.target.value)}
/>
)}
{inputType === 'date-range' && (
// https://github.com/airbnb/react-dates
<CustomDateRangePicker
// Required
startDate={getDateEntry(studyDateFrom, defaultStartDate)}
startDateId="start-date"
endDate={getDateEntry(studyDateTo, defaultEndDate)}
endDateId="end-date"
// TODO: We need a dynamic way to determine which fields values to update
onDatesChange={({ startDate, endDate, preset = false }) => {
onValueChange('studyDateFrom', startDate);
onValueChange('studyDateTo', endDate);
}}
focusedInput={focusedInput}
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
// Optional
numberOfMonths={1} // For med and small screens? 2 for large?
showClearDates={true}
anchorDirection="left"
presets={studyDatePresets}
hideKeyboardShortcutsPanel={true}
isOutsideRange={day => !isInclusivelyBeforeDay(day, moment())}
/>
)}
</th>
);
})
: null;
}
TableSearchFilter.propTypes = {
meta: PropTypes.arrayOf(
PropTypes.shape({
displayText: PropTypes.string.isRequired,
fieldName: PropTypes.string.isRequired,
inputType: PropTypes.oneOf(['text', 'date-range']).isRequired,
size: PropTypes.number.isRequired,
})
).isRequired,
values: PropTypes.object.isRequired,
onSort: PropTypes.func.isRequired,
sortFieldName: PropTypes.string,
sortDirection: PropTypes.oneOf([null, 'asc', 'desc']),
};
TableSearchFilter.defaultProps = {};
export { TableSearchFilter };
export default TableSearchFilter;
@@ -1,4 +0,0 @@
export { StudyList } from './StudyList.js';
export { TableSearchFilter } from './TableSearchFilter.js';
export { TablePagination } from './TablePagination.js';
export { PageToolbar } from './PageToolbar.js';
@@ -1,50 +0,0 @@
$headerRowHeight = 63px
.tableList
background-color: var(--primary-background-color)
height: 100%;
width: 100%;
.tableListHeader
background-color: var(--ui-gray-darker)
color: var(--text-secondary-color)
display: flex
height: $headerRowHeight
line-height: $headerRowHeight
margin-top: 2px
overflow: hidden
width: 100%
.tableListHeaderTitle
color: var(--text-secondary-color)
font-size: 22px
font-weight: 300
line-height: $headerRowHeight
padding: 0 10px
text-align: left
flex: 1;
.tableListHeaderSelector
cursor: pointer
max-width: 30px
padding-left: 2px
align-items: stretch
flex: 1
justify-content: space-around
text-align: center
svg
fill: #c1d8e3
height: 63px
max-width: 11px
.numberOfItems
color: var(--ui-sky-blue)
float: right
font-weight: 300
font-size: 40px
max-width: 54px
height: $headerRowHeight
line-height: 66px
flex: 1;
@@ -1,46 +0,0 @@
.tableListItem
display: flex
margin-left: -6px
margin-top: 2px
padding-left: 6px
opacity: 0.7
cursor: pointer;
width: calc(100% + 6px)
&:hover
opacity 1
&.selected
opacity 1
.itemIndex
color: $activeColor
&.hasWarning
.warning
display: block
.itemIndex
background-color: var(--ui-gray)
color: var(--text-secondary-color)
cursor: pointer
flex: 1
max-width: 25px
transition(all 0.3s ease)
font-size: 14px
font-weight: 400
margin-right: 6px
padding: 10px
text-align: center
-webkit-box-sizing: initial;
-moz-box-sizing: initial;
box-sizing: initial;
.itemContent
flex: 1
color: var(--text-secondary-color)
width: calc(100% - 51px);
font-weight: 400
margin-top: 9px;
.warning-icon
display: none
@@ -1,48 +0,0 @@
---
name: Table List
menu: Components
route: /components/table-list
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { TableList, TableListItem } from './../index.js'
# Table List
## Basic usage
<Playground>
<State initial={{
selectedIndex: null,
listItems: [
{ label: 'Brain Left' },
{ label: 'Brain Right' },
{ label: 'Heart Left' },
{ label: 'Heart Right' }
]
}}>
{({ state, setState }) => (
<TableList headerTitle='Example Table List Header'>
{state.listItems.map( (item, index) => {
return (
<TableListItem
key={`item_${index}`}
itemClass={state.selectedIndex === index ? 'selected' : ''}
itemIndex={index}
onItemClick={() => alert('item clicked')}
>
<label>{item.label}</label>
</TableListItem>
)}
)}
</TableList>
)}
</State>
</Playground>
## API
<Props of={TableList} />
@@ -1,2 +0,0 @@
export { TableList } from './TableList';
export { TableListItem } from './TableListItem';
@@ -1,65 +0,0 @@
import './ToolbarSection.styl';
import React, { PureComponent } from 'react';
import ExpandableToolMenu from '../../viewer/ExpandableToolMenu';
import PropTypes from 'prop-types';
import ToolbarButton from '../../viewer/ToolbarButton';
import classnames from 'classnames';
class ToolbarSection extends PureComponent {
static defaultProps = {
className: '',
};
static propTypes = {
buttons: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
label: PropTypes.string.isRequired,
icon: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
name: PropTypes.string.isRequired,
}),
]),
/** Optional: Expandable Tool Menu */
buttons: PropTypes.arrayOf(PropTypes.shape({})),
})
).isRequired,
/** Array of string button ids that should show as active */
activeButtons: PropTypes.arrayOf(PropTypes.string).isRequired,
/** Class for toolbar section container */
className: PropTypes.string,
};
render() {
const items = this.props.buttons.map((button, index) => {
if (button.buttons && Array.isArray(button.buttons)) {
return (
<ExpandableToolMenu
key={`expandable-${index}`}
{...button}
activeCommand={button.activeButton}
/>
);
} else {
return (
<ToolbarButton
key={index}
{...button}
isActive={this.props.activeButtons.includes(button.id)}
/>
);
}
});
return (
<div className={classnames('ToolbarSection', this.props.className)}>
{items}
</div>
);
}
}
export { ToolbarSection };
@@ -1,4 +0,0 @@
.ToolbarSection
float: left;
background-color: var(--primary-background-color);
border-radius: 8px;
@@ -1,41 +0,0 @@
export default [
{
id: 'Pan',
label: 'Pan',
icon: 'arrows',
},
{
label: 'Zoom',
icon: 'search',
},
{
label: 'Bidirectional',
icon: 'measure-target',
},
{
label: 'Stack Scroll',
icon: 'bars',
},
{
label: 'Reset',
icon: 'reset',
},
{
label: 'Manual',
icon: 'level',
},
{
label: 'More',
icon: 'ellipse-circle',
buttons: [
{
label: 'Reset 2',
icon: 'reset',
},
{
label: 'Manual 2',
icon: 'adjust',
},
],
},
];
@@ -1,39 +0,0 @@
---
name: Toolbar Section
menu: Components
route: /components/toolbar-section
---
import { Playground, Props } from 'docz';
import { State } from 'react-powerplug';
import { ToolbarSection } from './../index.js';
import exampleButtons from './exampleButtons.js';
import NameSpace from '../../../__docs__/NameSpace';
# ToolbarSection
A basic row of buttons for a toolbar. Active command is `WwwcTool`
## Basic usage
<Playground>
<State
initial={{
buttons: exampleButtons,
activeButtons: ['Pan'],
setToolActive: () => {
alert('pressed');
},
}}
>
{({ state, setState }) => <ToolbarSection {...state} />}
</State>
</Playground>
## API
<Props of={ToolbarSection} />
## Translation Namespace
<NameSpace name="Buttons" />
@@ -1 +0,0 @@
export { ToolbarSection } from './ToolbarSection.js';
@@ -1,66 +0,0 @@
import './Tooltip.styl';
import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
/** Sets the direction the Tooltip is positioned towards. */
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
/** The "top" position value for the Tooltip. */
positionTop: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** The "left" position value for the Tooltip. */
positionLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** The "top" position value for the Tooltip arrow. */
arrowOffsetTop: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** The "left" position value for the Tooltip arrow. */
arrowOffsetLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};
const defaultProps = {
placement: 'right',
};
class Tooltip extends React.Component {
render() {
const {
placement,
positionTop,
positionLeft,
arrowOffsetTop,
arrowOffsetLeft,
className,
style,
children,
} = this.props;
const outerStyle = {
top: positionTop,
left: positionLeft,
...style,
};
const arrowStyle = {
top: arrowOffsetTop,
left: arrowOffsetLeft,
};
return (
<div
role="tooltip"
className={classNames(className, 'tooltip', placement)}
style={outerStyle}
>
<div className="tooltip-arrow" style={arrowStyle} />
<div className="tooltip-inner">{children}</div>
</div>
);
}
}
Tooltip.propTypes = propTypes;
Tooltip.defaultProps = defaultProps;
export { Tooltip };
Loaded 100 of 284 files, more files were not shown because too many files have changed in this diff. Show more