itk-viewer-bootstrap-ui
Version:
[](https://badge.fury.io/js/itk-viewer-bootstrap-ui)  [ • 7.05 kB
JSX
import React, { useRef } from 'react'
import { useSelector } from '@xstate/react'
import {
visibleIconDataUri,
invisibleIconDataUri,
pauseIconDataUri,
playIconDataUri
} from '@itk-viewer/icons'
import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'
import Image from 'react-bootstrap/Image'
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import Tooltip from 'react-bootstrap/Tooltip'
import cn from 'classnames'
import Badge from 'react-bootstrap/Badge'
import '../style.css'
const planes = ['x', 'y', 'z']
const PLANE_VIEW_MODES = ['XPlane', 'YPlane', 'ZPlane']
function PlaneSliders({ service }) {
const { send } = service
const xVisibility = useRef(null)
const yVisibility = useRef(null)
const zVisibility = useRef(null)
const xScroll = useRef(null)
const yScroll = useRef(null)
const zScroll = useRef(null)
const visRefs = [xVisibility, yVisibility, zVisibility]
const scrollRefs = [xScroll, yScroll, zScroll]
const slicingPlanes = useSelector(
service,
(state) => state.context.main.slicingPlanes
)
const hidden = useSelector(service, (state) => {
// Changing view mode does not change slicingPlanes visible prop
const planeModeOn = PLANE_VIEW_MODES.some((mode) => {
return mode === state.context.main.viewMode
})
// slicingPlanes visible prop only changed on 3D "View Planes" button
// This detects if 3D View Planes is on
const { slicingPlanes } = state.context.main
const isPlaneVisible =
slicingPlanes.x.visible ||
slicingPlanes.y.visible ||
slicingPlanes.z.visible
return state.context.uiCollapsed || (!planeModeOn && !isPlaneVisible)
})
const stateContextUse2D = useSelector(service, (state) => state.context.use2D)
// Force re-render so sliders disappear in 2D mode
const viewMode = useSelector(service, (state) => state.context.main.viewMode)
// Force re-render for 2D view modes
const planeSlice2D = planes.map((plane) =>
useSelector(service, (state) => state.context.main[`${plane}Slice`])
)
// Force re-render for visibility button
const slicingPlanesVisibility = planes.map((plane) =>
useSelector(
service,
(state) => state.context.main.slicingPlanes[`${plane}`].visible
)
)
// Force re-render for play/pause button
const slicingPlanesScroll = useSelector(
service,
(state) =>
planes.map(
(plane) => state.context.main.slicingPlanes[`${plane}`].scroll
),
(a, b) => a.length === b.length && a.every((x, i) => x === b[i])
)
const toggleVisibility = (plane) => {
slicingPlanes[plane].visible = !slicingPlanes[plane].visible
send({ type: 'SLICING_PLANES_CHANGED', data: slicingPlanes })
}
const togglePlay = (plane) => {
slicingPlanes[plane].scroll = !slicingPlanes[plane].scroll
if (slicingPlanes[plane].scroll && !slicingPlanes[plane].visible) {
toggleVisibility(plane)
}
send({ type: 'SLICING_PLANES_CHANGED', data: slicingPlanes })
}
const handleSliderChange = (plane, val) => {
send({
type: `${plane.toUpperCase()}_SLICE_CHANGED`,
data: Number(val)
})
}
const sliderVisible = (plane) => {
const isVolume = viewMode === 'Volume' && !stateContextUse2D
const planeVisible = viewMode === `${plane.toUpperCase()}Plane`
if (!isVolume && !planeVisible) {
return 'hiddenSlider'
}
return ''
}
return (
<div hidden={hidden}>
{planes.map((plane, idx) => {
return (
planeSlice2D[idx] !== null &&
!service.machine.context.use2D && (
<div key={plane.toUpperCase()}>
<div className="input-group">
<OverlayTrigger
transition={false}
overlay={
<Tooltip>{plane.toUpperCase()} Plane Visibility</Tooltip>
}
>
<Button
className={cn(
`icon-button ${
viewMode !== 'Volume' ? 'hiddenSlider' : ''
}`,
{
checked: slicingPlanesVisibility[idx]
}
)}
onClick={(_e) => {
toggleVisibility(plane)
}}
variant="secondary"
ref={visRefs[idx]}
>
{slicingPlanes[plane].visible ? (
<Image src={visibleIconDataUri}></Image>
) : (
<Image src={invisibleIconDataUri}></Image>
)}
</Button>
</OverlayTrigger>
<OverlayTrigger
transition={false}
overlay={
<Tooltip>{`${plane.toUpperCase()} Plane Toggle Scroll`}</Tooltip>
}
>
<Button
className={cn(
`icon-button ${
viewMode !== `${plane.toUpperCase()}Plane` &&
viewMode !== 'Volume'
? 'hiddenSlider'
: ''
}`,
{
checked: slicingPlanesScroll[idx]
}
)}
onClick={(_e) => {
togglePlay(plane)
}}
variant="secondary"
ref={scrollRefs[idx]}
>
{slicingPlanes[plane].scroll ? (
<Image src={pauseIconDataUri}></Image>
) : (
<Image src={playIconDataUri}></Image>
)}
</Button>
</OverlayTrigger>
<Badge
className={`labelBadge ${plane}badge ${
viewMode !== `${plane.toUpperCase()}Plane` &&
viewMode !== 'Volume'
? 'hiddenSlider'
: ''
}`}
bg="secondary"
>
{plane.toUpperCase()}: {planeSlice2D[idx]?.toPrecision(4)}
</Badge>
<div
key={plane.toUpperCase()}
className={`planeSliders ${sliderVisible(plane)}`}
>
<Form.Control
type="range"
custom
min={slicingPlanes[plane].min}
max={slicingPlanes[plane].max}
value={planeSlice2D[idx]}
step={slicingPlanes[plane].step}
onChange={(_e) => {
handleSliderChange(plane, _e.target.value)
}}
/>
</div>
</div>
</div>
)
)
})}
</div>
)
}
export default PlaneSliders