Skip to content

Commit b3024c2

Browse files
feat: add right menu highlight (#285)
* fix: annotation selection styles * fix: infinite re-renders * fix: formatAnnotation type * fix: linting * fix: import * fix: linting * feat: make right panel highlight selected ROIs * fix: linting * feat: include right panel select on click * feat: use viewport clicked event * fix: use type safe comparison * fix: explicitily bind this * fix: revert method name * fix: revert prop order * fix: remove redundant undefined * chore: use correct event payload --------- Co-authored-by: Igor Octaviano <igoroctaviano@gmail.com>
1 parent da17f99 commit b3024c2

2 files changed

Lines changed: 118 additions & 44 deletions

File tree

src/components/AnnotationList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface AnnotationListProps {
1313
roiUID: string
1414
isVisible: boolean
1515
}) => void
16-
onSelection: ({ roiUID }: { roiUID: string }) => void
16+
onSelection: (uid: string) => void
1717
}
1818

1919
/**
@@ -43,7 +43,7 @@ class AnnotationList extends React.Component<AnnotationListProps, {}> {
4343
}
4444

4545
handleMenuItemSelection (object: any): void {
46-
this.props.onSelection({ roiUID: object.key })
46+
this.props.onSelection(object.key)
4747
}
4848

4949
render (): React.ReactNode {

src/components/SlideViewer.tsx

Lines changed: 116 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
458458

459459
private lastPixel = [0, 0] as [number, number]
460460

461+
private readonly keysDown = new Set<string>()
462+
461463
private readonly defaultRoiStyle: dmv.viewer.ROIStyleOptions = {
462464
stroke: {
463465
color: DEFAULT_ROI_STROKE_COLOR,
@@ -482,15 +484,16 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
482484
[annotationUID: string]: StyleOptions
483485
} = {}
484486

485-
private readonly selectionColor: number[] = [140, 184, 198]
487+
private readonly selectionStrokeColor: number[] = [0, 153, 255]
488+
private readonly selectionFillColor: number[] = [255, 255, 255]
486489

487490
private readonly selectedRoiStyle: dmv.viewer.ROIStyleOptions = {
488-
stroke: { color: [...this.selectionColor, 1], width: 3 },
489-
fill: { color: [...this.selectionColor, 0.2] },
491+
stroke: { color: [...this.selectionStrokeColor, 1], width: 3 },
492+
fill: { color: [...this.selectionFillColor, 0.5] },
490493
image: {
491494
circle: {
492495
radius: 5,
493-
fill: { color: [...this.selectionColor, 1] }
496+
fill: { color: [...this.selectionStrokeColor, 1] }
494497
}
495498
}
496499
}
@@ -548,7 +551,6 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
548551

549552
this.componentSetup = this.componentSetup.bind(this)
550553
this.componentCleanup = this.componentCleanup.bind(this)
551-
552554
this.onWindowResize = this.onWindowResize.bind(this)
553555
this.handleRoiDrawing = this.handleRoiDrawing.bind(this)
554556
this.handleRoiTranslation = this.handleRoiTranslation.bind(this)
@@ -563,7 +565,6 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
563565
this.handleAnnotationEvaluationSelection = this.handleAnnotationEvaluationSelection.bind(this)
564566
this.handleAnnotationEvaluationClearance = this.handleAnnotationEvaluationClearance.bind(this)
565567
this.handleAnnotationConfigurationCompletion = this.handleAnnotationConfigurationCompletion.bind(this)
566-
this.handleAnnotationSelection = this.handleAnnotationSelection.bind(this)
567568
this.handleAnnotationVisibilityChange = this.handleAnnotationVisibilityChange.bind(this)
568569
this.handleAnnotationGroupVisibilityChange = this.handleAnnotationGroupVisibilityChange.bind(this)
569570
this.handleAnnotationGroupStyleChange = this.handleAnnotationGroupStyleChange.bind(this)
@@ -587,6 +588,7 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
587588
this.handlePresentationStateSelection = this.handlePresentationStateSelection.bind(this)
588589
this.handlePresentationStateReset = this.handlePresentationStateReset.bind(this)
589590
this.handleICCProfilesToggle = this.handleICCProfilesToggle.bind(this)
591+
this.handleAnnotationSelection = this.handleAnnotationSelection.bind(this)
590592

591593
const { volumeViewer, labelViewer } = _constructViewers({
592594
clients: this.props.clients,
@@ -1680,21 +1682,98 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
16801682
}
16811683
}
16821684

1683-
onRoiSelected = (event: CustomEventInit): void => {
1684-
const selectedRoi = event.detail.payload as dmv.roi.ROI | null
1685-
if (selectedRoi == null) {
1686-
this.setState({
1687-
selectedRoiUIDs: new Set(),
1688-
selectedRoi: undefined
1689-
})
1690-
return
1685+
getUpdatedSelectedRois = (newSelectedRoiUid?: string): { selectedRoiUIDs: Set<string>, selectedRoi?: dmv.roi.ROI} => {
1686+
const selectedRoiUid = newSelectedRoiUid
1687+
const emptySelection = {
1688+
selectedRoiUIDs: new Set<string>(),
1689+
selectedRoi: undefined
1690+
}
1691+
1692+
if (selectedRoiUid === undefined) {
1693+
return emptySelection
1694+
}
1695+
1696+
const selectedRoi = this.volumeViewer.getROI(selectedRoiUid)
1697+
if (selectedRoi === undefined) {
1698+
return emptySelection
16911699
}
16921700

16931701
console.debug(`selected ROI "${selectedRoi.uid}"`)
1702+
1703+
if (!this.keysDown.has('Shift')) {
1704+
return {
1705+
selectedRoiUIDs: new Set([selectedRoi.uid]),
1706+
selectedRoi
1707+
}
1708+
}
1709+
16941710
const oldSelectedRois = Array.from(this.state.selectedRoiUIDs)
1695-
this.setState({
1711+
return {
16961712
selectedRoiUIDs: new Set([...oldSelectedRois, selectedRoi.uid]),
1697-
selectedRoi: selectedRoi
1713+
selectedRoi
1714+
}
1715+
}
1716+
1717+
resetUnselectedRoiStyles = (selectionState: { selectedRoiUIDs: Set<string> }): void => {
1718+
this.volumeViewer.getAllROIs().forEach(roi => {
1719+
const uid = roi.uid
1720+
1721+
if (selectionState.selectedRoiUIDs.has(uid) || !this.state.visibleRoiUIDs.has(uid)) {
1722+
return
1723+
}
1724+
1725+
const key = _getRoiKey(roi)
1726+
const style = this.getRoiStyle(key)
1727+
this.volumeViewer.setROIStyle(uid, style)
1728+
})
1729+
}
1730+
1731+
onMapClicked = (event: CustomEventInit): void => {
1732+
const roisClicked = (event.detail?.payload?.rois ?? []) as dmv.roi.ROI[]
1733+
1734+
if (roisClicked.length !== 0) {
1735+
return
1736+
}
1737+
1738+
const updatedSelectedRois = this.getUpdatedSelectedRois()
1739+
this.setState(updatedSelectedRois)
1740+
1741+
// @ts-expect-error
1742+
this.volumeViewer.clearSelections()
1743+
1744+
this.resetUnselectedRoiStyles(updatedSelectedRois)
1745+
}
1746+
1747+
onRoiSelected = (event: CustomEventInit): void => {
1748+
const selectedRoiUid = event.detail?.payload?.uid as string
1749+
const updatedSelectedRois = this.getUpdatedSelectedRois(selectedRoiUid)
1750+
this.setState(updatedSelectedRois)
1751+
1752+
this.resetUnselectedRoiStyles(updatedSelectedRois)
1753+
}
1754+
1755+
handleAnnotationSelection (uid: string): void {
1756+
// @ts-expect-error
1757+
this.volumeViewer.clearSelections()
1758+
1759+
const updatedSelectedRois = this.getUpdatedSelectedRois(uid)
1760+
this.setState(updatedSelectedRois)
1761+
this.volumeViewer.getAllROIs().forEach((roi) => {
1762+
let style = {}
1763+
if (updatedSelectedRois.selectedRoiUIDs.has(roi.uid)) {
1764+
style = this.selectedRoiStyle
1765+
this.setState(state => {
1766+
const visibleRoiUIDs = state.visibleRoiUIDs
1767+
visibleRoiUIDs.add(roi.uid)
1768+
return { visibleRoiUIDs }
1769+
})
1770+
} else {
1771+
if (this.state.visibleRoiUIDs.has(roi.uid)) {
1772+
const key = _getRoiKey(roi)
1773+
style = this.getRoiStyle(key)
1774+
}
1775+
}
1776+
this.volumeViewer.setROIStyle(roi.uid, style)
16981777
})
16991778
}
17001779

@@ -1834,6 +1913,10 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
18341913
'dicommicroscopyviewer_roi_drawn',
18351914
this.onRoiDrawn
18361915
)
1916+
document.body.removeEventListener(
1917+
'dicommicroscopyviewer_viewport_clicked',
1918+
this.onMapClicked
1919+
)
18371920
document.body.removeEventListener(
18381921
'dicommicroscopyviewer_roi_selected',
18391922
this.onRoiSelected
@@ -1874,6 +1957,10 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
18741957
'keyup',
18751958
this.onKeyUp
18761959
)
1960+
document.body.removeEventListener(
1961+
'keyup',
1962+
this.onKeyDown
1963+
)
18771964
window.removeEventListener('resize', this.onWindowResize)
18781965

18791966
this.volumeViewer.cleanup()
@@ -1890,7 +1977,12 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
18901977
*/
18911978
}
18921979

1980+
onKeyDown = (event: KeyboardEvent): void => {
1981+
this.keysDown.add(event.key)
1982+
}
1983+
18931984
onKeyUp = (event: KeyboardEvent): void => {
1985+
this.keysDown.delete(event.key)
18941986
if (event.key === 'Escape') {
18951987
if (this.state.isRoiDrawingActive) {
18961988
console.info('deactivate drawing of ROIs')
@@ -1949,6 +2041,10 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
19492041
'dicommicroscopyviewer_roi_selected',
19502042
this.onRoiSelected
19512043
)
2044+
document.body.addEventListener(
2045+
'dicommicroscopyviewer_viewport_clicked',
2046+
this.onMapClicked
2047+
)
19522048
document.body.addEventListener(
19532049
'dicommicroscopyviewer_roi_double_clicked',
19542050
this.onRoiDoubleClicked
@@ -1993,6 +2089,10 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
19932089
'keyup',
19942090
this.onKeyUp
19952091
)
2092+
document.body.addEventListener(
2093+
'keydown',
2094+
this.onKeyDown
2095+
)
19962096
window.addEventListener('beforeunload', this.componentCleanup)
19972097
window.addEventListener('resize', this.onWindowResize)
19982098
}
@@ -2524,32 +2624,6 @@ class SlideViewer extends React.Component<SlideViewerProps, SlideViewerState> {
25242624
})
25252625
}
25262626

2527-
/**
2528-
* Handler that gets called when an annotation has been selected from the
2529-
* current list of annotations.
2530-
*/
2531-
handleAnnotationSelection ({ roiUID }: { roiUID: string }): void {
2532-
console.log(`selected ROI ${roiUID}`)
2533-
this.setState({ selectedRoiUIDs: new Set([roiUID]) })
2534-
this.volumeViewer.getAllROIs().forEach((roi) => {
2535-
let style = {}
2536-
if (roi.uid === roiUID) {
2537-
style = this.selectedRoiStyle
2538-
this.setState(state => {
2539-
const visibleRoiUIDs = state.visibleRoiUIDs
2540-
visibleRoiUIDs.add(roi.uid)
2541-
return { visibleRoiUIDs }
2542-
})
2543-
} else {
2544-
if (this.state.visibleRoiUIDs.has(roi.uid)) {
2545-
const key = _getRoiKey(roi)
2546-
style = this.getRoiStyle(key)
2547-
}
2548-
}
2549-
this.volumeViewer.setROIStyle(roi.uid, style)
2550-
})
2551-
}
2552-
25532627
/**
25542628
* Handle toggling of annotation visibility, i.e., whether a given
25552629
* annotation should be either displayed or hidden by the viewer.

0 commit comments

Comments
 (0)