From 929b9cceaeb5f9df23c38ac359e873a7eebd29a5 Mon Sep 17 00:00:00 2001 From: Igor Octaviano Date: Tue, 29 Jul 2025 11:31:31 -0300 Subject: [PATCH 01/13] Add selector for ANN groups --- src/components/SlideViewer.tsx | 102 +++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/src/components/SlideViewer.tsx b/src/components/SlideViewer.tsx index 958568d5..a50243f3 100644 --- a/src/components/SlideViewer.tsx +++ b/src/components/SlideViewer.tsx @@ -54,6 +54,7 @@ import HoveredRoiTooltip from './HoveredRoiTooltip' import { adaptRoiToAnnotation } from '../services/RoiToAnnotationAdapter' import generateReport from '../utils/generateReport' import { runValidations } from '../contexts/ValidationContext' +import DicomMetadataStore from '../services/DICOMMetadataStore' const DEFAULT_ROI_STROKE_COLOR: number[] = [255, 234, 0] // [0, 126, 163] const DEFAULT_ROI_FILL_COLOR: number[] = [255, 234, 0, 0.2] // [0, 126, 163, 0.2] @@ -423,6 +424,7 @@ interface SlideViewerState { validYCoordinateRange: number[] selectedMagnification?: number areRoisHidden: boolean + selectedSeriesInstanceUID?: string pixelDataStatistics: { [opticalPathIdentifier: string]: { min: number @@ -645,6 +647,7 @@ class SlideViewer extends React.Component { validYCoordinateRange: [offset[1], offset[1] + size[1]], selectedMagnification: undefined, areRoisHidden: false, + selectedSeriesInstanceUID: undefined, pixelDataStatistics: {}, selectedPresentationStateUID: this.props.selectedPresentationStateUID, loadingFrames: new Set(), @@ -3118,6 +3121,27 @@ class SlideViewer extends React.Component { this.volumeViewer.zoomToROI(annotationGroupUID) } + handleAnnotationGroupSelection = (value: string): void => { + this.setState({ selectedSeriesInstanceUID: value }) + } + + getSeriesDescription = (seriesInstanceUID: string): string => { + // Get the study from DicomMetadataStore + const study = DicomMetadataStore.getStudy(this.props.studyInstanceUID) + + if ((study?.series) != null) { + // Find the series that matches this series instance UID + const series = study.series.find(s => s.SeriesInstanceUID === seriesInstanceUID) + + if (series?.SeriesDescription !== undefined && series.SeriesDescription !== '') { + return series.SeriesDescription + } + } + + // Fallback to truncated UID if no description found + return `Series ${seriesInstanceUID.slice(0, 8)}...` + } + /** * Handler that will toggle the ICC profile color management, i.e., either * enable or disable it, depending on its current state. @@ -3481,19 +3505,75 @@ class SlideViewer extends React.Component { annotationGroup.uid ) }) + + // Group annotation groups by seriesInstanceUID + const annotationGroupsBySeries: { [seriesInstanceUID: string]: dmv.annotation.AnnotationGroup[] } = {} + annotationGroups.forEach(annotationGroup => { + const seriesUID = annotationGroup.seriesInstanceUID + if (!(seriesUID in annotationGroupsBySeries)) { + annotationGroupsBySeries[seriesUID] = [] + } + annotationGroupsBySeries[seriesUID].push(annotationGroup) + }) + + // Initialize selected series if not set + if (this.state.selectedSeriesInstanceUID === undefined && annotationGroups.length !== 0) { + this.setState({ selectedSeriesInstanceUID: 'all' }) + } + + // Create dropdown options for series + const dropdownOptions = [ + { + value: 'all', + label: 'All' + }, + ...Object.keys(annotationGroupsBySeries).map((seriesUID) => ({ + value: seriesUID, + label: `${this.getSeriesDescription(seriesUID)} (${annotationGroupsBySeries[seriesUID]?.length ?? 0} groups)` + })) + ] + + // Get annotation groups for the selected series or all series + const selectedSeriesAnnotationGroups = this.state.selectedSeriesInstanceUID === 'all' + ? annotationGroups + : (this.state.selectedSeriesInstanceUID !== undefined + ? annotationGroupsBySeries[this.state.selectedSeriesInstanceUID] ?? [] + : []) + annotationGroupMenu = ( - + {/* Series Selection Dropdown */} +
+ { ) } + return undefined + } - let segmentationMenu + private getSegmentationMenu = (segments: dmv.segment.Segment[]): React.ReactNode => { if (segments.length > 0) { const defaultSegmentStyles: { [segmentUID: string]: { @@ -3426,7 +3460,6 @@ class SlideViewer extends React.Component { const segmentMetadata: { [segmentUID: string]: dmv.metadata.Segmentation[] } = {} - const segments = this.volumeViewer.getAllSegments() segments.forEach(segment => { defaultSegmentStyles[segment.uid] = this.volumeViewer.getSegmentStyle( segment.uid @@ -3435,7 +3468,7 @@ class SlideViewer extends React.Component { segment.uid ) }) - segmentationMenu = ( + return ( { /> ) - openSubMenuItems.push('segmentations') } + return undefined + } - let parametricMapMenu + private getParametricMapMenu = (mappings: dmv.mapping.ParameterMapping[]): React.ReactNode => { if (mappings.length > 0) { const defaultMappingStyles: { [mappingUID: string]: { @@ -3468,7 +3502,7 @@ class SlideViewer extends React.Component { mapping.uid ) }) - parametricMapMenu = ( + return ( { /> ) - openSubMenuItems.push('parametric-maps') } + return undefined + } - let annotationGroupMenu - - annotations?.forEach?.(this.formatAnnotation.bind(this)) - + private getAnnotationGroupMenu = (annotationGroups: dmv.annotation.AnnotationGroup[]): React.ReactNode => { if (annotationGroups.length > 0) { const annotationGroupMetadata: { [annotationGroupUID: string]: dmv.metadata.MicroscopyBulkSimpleAnnotations @@ -3540,7 +3572,7 @@ class SlideViewer extends React.Component { ? annotationGroupsBySeries[this.state.selectedSeriesInstanceUID] ?? [] : []) - annotationGroupMenu = ( + return ( {/* Series Selection Dropdown */}
{ )} ) - openSubMenuItems.push('annotationGroups') } + return undefined + } - let toolbar - let toolbarHeight = '0px' + private getToolbar = (): { toolbar: React.ReactNode, toolbarHeight: string } => { const annotationTools = [ + + +
+ ), + filterIcon: (filtered: boolean) => ( + - - - - -
- ), - filterIcon: (filtered: boolean) => ( - - ) - }) + ) + } + } } export default withRouter(Worklist) diff --git a/src/setupTests.tsx b/src/setupTests.tsx index dfbf34b9..76a65db6 100644 --- a/src/setupTests.tsx +++ b/src/setupTests.tsx @@ -6,11 +6,24 @@ import '@testing-library/jest-dom' global.matchMedia = global.matchMedia !== undefined ? global.matchMedia - : function () { + : function (query: string): MediaQueryList { return { + media: query, matches: false, - addListener: function () {}, - removeListener: function () {} + onchange: null, + addListener () { + // Mock implementation - intentionally empty + }, + removeListener () { + // Mock implementation - intentionally empty + }, + addEventListener () { + // Mock implementation - intentionally empty + }, + removeEventListener () { + // Mock implementation - intentionally empty + }, + dispatchEvent () { return false } } } From 683ba15c1af569cac01875a5bf0d3e7b45720085 Mon Sep 17 00:00:00 2001 From: Igor Octaviano Date: Tue, 29 Jul 2025 17:05:50 -0300 Subject: [PATCH 08/13] Address deep source issues --- src/App.tsx | 1 + src/AppConfig.d.ts | 1 + src/DicomWebManager.ts | 3 + src/components/AnnotationGroupItem.tsx | 2 + src/components/AnnotationGroupList.tsx | 2 + src/components/AnnotationItem.tsx | 2 + src/components/AnnotationList.tsx | 1 + src/components/Button.tsx | 2 +- src/components/CaseViewer.tsx | 1 + src/components/ClinicalTrial.tsx | 1 + src/components/Equipment.tsx | 1 + src/components/MappingItem.tsx | 1 + src/components/MappingList.tsx | 1 + src/components/OpticalPathItem.tsx | 2 + src/components/OpticalPathList.tsx | 1 + src/components/Patient.tsx | 1 + src/components/Report.tsx | 2 + src/components/SegmentItem.tsx | 1 + src/components/SegmentList.tsx | 1 + src/components/SlideItem.tsx | 1 + src/components/SlideViewer.tsx | 126 +++++++----------- src/components/SlideViewer/GoToModal.tsx | 6 +- .../SlideViewer/SlideViewerSidebar.tsx | 3 +- src/components/SlideViewer/types.ts | 2 + src/components/SlideViewer/utils/roiUtils.ts | 2 + .../SlideViewer/utils/viewerUtils.ts | 3 + src/components/SpecimenItem.tsx | 2 + src/components/SpecimenList.tsx | 1 + src/components/Study.tsx | 1 + src/components/Worklist.tsx | 54 ++++---- src/components/__tests__/Worklist.test.tsx | 1 + src/data/slides.tsx | 1 + src/data/specimens.tsx | 1 + src/services/RoiToAnnotationAdapter.ts | 2 + src/services/fetchImageMetadata.ts | 1 + src/types/annotations.ts | 1 + src/utils/sr.tsx | 1 + src/utils/values.ts | 1 + types/dicom-microscopy-viewer/index.d.ts | 6 +- 39 files changed, 133 insertions(+), 110 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 4f8903ee..187fd421 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import { } from 'react-router-dom' import { Layout, message } from 'antd' import { FaSpinner } from 'react-icons/fa' +// skipcq: JS-C1003 import * as dwc from 'dicomweb-client' import AppConfig, { ServerSettings, ErrorMessageSettings } from './AppConfig' diff --git a/src/AppConfig.d.ts b/src/AppConfig.d.ts index 08c51e31..62376298 100644 --- a/src/AppConfig.d.ts +++ b/src/AppConfig.d.ts @@ -1,3 +1,4 @@ +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' export type DicomWebManagerErrorHandler = ( diff --git a/src/DicomWebManager.ts b/src/DicomWebManager.ts index 4c5838c8..d01b0edd 100644 --- a/src/DicomWebManager.ts +++ b/src/DicomWebManager.ts @@ -1,5 +1,8 @@ +// skipcq: JS-C1003 import * as dwc from 'dicomweb-client' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { ServerSettings, DicomWebManagerErrorHandler } from './AppConfig' diff --git a/src/components/AnnotationGroupItem.tsx b/src/components/AnnotationGroupItem.tsx index 387eb73c..acb3d790 100644 --- a/src/components/AnnotationGroupItem.tsx +++ b/src/components/AnnotationGroupItem.tsx @@ -15,7 +15,9 @@ import { } from 'antd' import { SettingOutlined } from '@ant-design/icons' import { FaEye, FaEyeSlash } from 'react-icons/fa' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import Description from './Description' diff --git a/src/components/AnnotationGroupList.tsx b/src/components/AnnotationGroupList.tsx index 80256544..d43b75c2 100644 --- a/src/components/AnnotationGroupList.tsx +++ b/src/components/AnnotationGroupList.tsx @@ -1,6 +1,8 @@ import React from 'react' import { Menu, Switch } from 'antd' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import AnnotationGroupItem from './AnnotationGroupItem' diff --git a/src/components/AnnotationItem.tsx b/src/components/AnnotationItem.tsx index 4f0081c1..fb43f33e 100644 --- a/src/components/AnnotationItem.tsx +++ b/src/components/AnnotationItem.tsx @@ -1,5 +1,7 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import { Menu, Space, Switch } from 'antd' import { FaEye, FaEyeSlash } from 'react-icons/fa' diff --git a/src/components/AnnotationList.tsx b/src/components/AnnotationList.tsx index 8e56b945..ccad0fdc 100644 --- a/src/components/AnnotationList.tsx +++ b/src/components/AnnotationList.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { Menu, Switch } from 'antd' import { FaEye, FaEyeSlash } from 'react-icons/fa' diff --git a/src/components/Button.tsx b/src/components/Button.tsx index f264f2ae..c7e17dd6 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -3,7 +3,7 @@ import { Button as Btn, Divider, Tooltip } from 'antd' import { IconType } from 'react-icons' interface ButtonProps { - icon: IconType | React.ComponentType + icon: IconType | React.ComponentType<{}> tooltip?: string label?: string onClick?: (options: React.SyntheticEvent) => void diff --git a/src/components/CaseViewer.tsx b/src/components/CaseViewer.tsx index 16f420ab..5dc79baf 100644 --- a/src/components/CaseViewer.tsx +++ b/src/components/CaseViewer.tsx @@ -1,5 +1,6 @@ import { Routes, Route, useLocation, useParams } from 'react-router-dom' import { Layout, Menu } from 'antd' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import { useEffect, useState } from 'react' diff --git a/src/components/ClinicalTrial.tsx b/src/components/ClinicalTrial.tsx index 102262a9..e32607a5 100644 --- a/src/components/ClinicalTrial.tsx +++ b/src/components/ClinicalTrial.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import Description from './Description' diff --git a/src/components/Equipment.tsx b/src/components/Equipment.tsx index 2223825c..c6d81473 100644 --- a/src/components/Equipment.tsx +++ b/src/components/Equipment.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import Description from './Description' diff --git a/src/components/MappingItem.tsx b/src/components/MappingItem.tsx index 936ffb77..23923005 100644 --- a/src/components/MappingItem.tsx +++ b/src/components/MappingItem.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { Button, diff --git a/src/components/MappingList.tsx b/src/components/MappingList.tsx index c4a438b9..4e570992 100644 --- a/src/components/MappingList.tsx +++ b/src/components/MappingList.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { Menu } from 'antd' diff --git a/src/components/OpticalPathItem.tsx b/src/components/OpticalPathItem.tsx index d664a4eb..3d11de38 100644 --- a/src/components/OpticalPathItem.tsx +++ b/src/components/OpticalPathItem.tsx @@ -20,7 +20,9 @@ import { SettingOutlined } from '@ant-design/icons' import Description from './Description' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import { SpecimenPreparationStepItems } from '../data/specimens' diff --git a/src/components/OpticalPathList.tsx b/src/components/OpticalPathList.tsx index 40b47a18..554a1e71 100644 --- a/src/components/OpticalPathList.tsx +++ b/src/components/OpticalPathList.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { Button as Btn, Menu, Select, Space, Tooltip } from 'antd' import { AppstoreAddOutlined } from '@ant-design/icons' diff --git a/src/components/Patient.tsx b/src/components/Patient.tsx index cf5b7743..3d1a1a82 100644 --- a/src/components/Patient.tsx +++ b/src/components/Patient.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import Description from './Description' diff --git a/src/components/Report.tsx b/src/components/Report.tsx index f7fd9170..b7028c01 100644 --- a/src/components/Report.tsx +++ b/src/components/Report.tsx @@ -1,5 +1,7 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import { Divider } from 'antd' import { v4 as generateUUID } from 'uuid' diff --git a/src/components/SegmentItem.tsx b/src/components/SegmentItem.tsx index 5f942603..d0d3cbda 100644 --- a/src/components/SegmentItem.tsx +++ b/src/components/SegmentItem.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { Button, diff --git a/src/components/SegmentList.tsx b/src/components/SegmentList.tsx index e987266b..4d322929 100644 --- a/src/components/SegmentList.tsx +++ b/src/components/SegmentList.tsx @@ -1,4 +1,5 @@ import React from 'react' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { Menu } from 'antd' diff --git a/src/components/SlideItem.tsx b/src/components/SlideItem.tsx index 747f4ac7..38d977cf 100644 --- a/src/components/SlideItem.tsx +++ b/src/components/SlideItem.tsx @@ -1,5 +1,6 @@ import React from 'react' import { FaSpinner } from 'react-icons/fa' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { Menu } from 'antd' diff --git a/src/components/SlideViewer.tsx b/src/components/SlideViewer.tsx index ae4e82f6..30a28e17 100644 --- a/src/components/SlideViewer.tsx +++ b/src/components/SlideViewer.tsx @@ -12,8 +12,11 @@ import { FaTrash, FaSave } from 'react-icons/fa' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' +// skipcq: JS-C1003 import * as dwc from 'dicomweb-client' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import { withRouter } from '../utils/router' import { StorageClasses } from '../data/uids' @@ -187,47 +190,6 @@ class SlideViewer extends React.Component { } }) - this.componentSetup = this.componentSetup.bind(this) - this.componentCleanup = this.componentCleanup.bind(this) - this.onWindowResize = this.onWindowResize.bind(this) - this.handleRoiDrawing = this.handleRoiDrawing.bind(this) - this.handleRoiTranslation = this.handleRoiTranslation.bind(this) - this.handleRoiModification = this.handleRoiModification.bind(this) - this.handleRoiVisibilityChange = this.handleRoiVisibilityChange.bind(this) - this.handleRoiRemoval = this.handleRoiRemoval.bind(this) - this.handleRoiSelectionCancellation = this.handleRoiSelectionCancellation.bind(this) - this.handleAnnotationConfigurationCancellation = this.handleAnnotationConfigurationCancellation.bind(this) - this.handleAnnotationGeometryTypeSelection = this.handleAnnotationGeometryTypeSelection.bind(this) - this.handleAnnotationMeasurementActivation = this.handleAnnotationMeasurementActivation.bind(this) - this.handleAnnotationFindingSelection = this.handleAnnotationFindingSelection.bind(this) - this.handleAnnotationEvaluationSelection = this.handleAnnotationEvaluationSelection.bind(this) - this.handleAnnotationEvaluationClearance = this.handleAnnotationEvaluationClearance.bind(this) - this.handleAnnotationConfigurationCompletion = this.handleAnnotationConfigurationCompletion.bind(this) - this.handleAnnotationVisibilityChange = this.handleAnnotationVisibilityChange.bind(this) - this.handleAnnotationGroupVisibilityChange = this.handleAnnotationGroupVisibilityChange.bind(this) - this.handleAnnotationGroupStyleChange = this.handleAnnotationGroupStyleChange.bind(this) - this.handleRoiStyleChange = this.handleRoiStyleChange.bind(this) - this.handleGoTo = this.handleGoTo.bind(this) - this.handleXCoordinateSelection = this.handleXCoordinateSelection.bind(this) - this.handleYCoordinateSelection = this.handleYCoordinateSelection.bind(this) - this.handleMagnificationSelection = this.handleMagnificationSelection.bind(this) - this.handleSlidePositionSelection = this.handleSlidePositionSelection.bind(this) - this.handleSlidePositionSelectionCancellation = this.handleSlidePositionSelectionCancellation.bind(this) - this.handleReportGeneration = this.handleReportGeneration.bind(this) - this.handleReportVerification = this.handleReportVerification.bind(this) - this.handleReportCancellation = this.handleReportCancellation.bind(this) - this.handleSegmentVisibilityChange = this.handleSegmentVisibilityChange.bind(this) - this.handleSegmentStyleChange = this.handleSegmentStyleChange.bind(this) - this.handleMappingVisibilityChange = this.handleMappingVisibilityChange.bind(this) - this.handleMappingStyleChange = this.handleMappingStyleChange.bind(this) - this.handleOpticalPathVisibilityChange = this.handleOpticalPathVisibilityChange.bind(this) - this.handleOpticalPathStyleChange = this.handleOpticalPathStyleChange.bind(this) - this.handleOpticalPathActivityChange = this.handleOpticalPathActivityChange.bind(this) - this.handlePresentationStateSelection = this.handlePresentationStateSelection.bind(this) - this.handlePresentationStateReset = this.handlePresentationStateReset.bind(this) - this.handleICCProfilesToggle = this.handleICCProfilesToggle.bind(this) - this.handleAnnotationSelection = this.handleAnnotationSelection.bind(this) - const { volumeViewer, labelViewer } = constructViewers({ clients: this.props.clients, slide: this.props.slide, @@ -847,7 +809,7 @@ class SlideViewer extends React.Component { annotations.forEach(ann => { try { this.volumeViewer.addAnnotationGroups(ann) - } catch (error: any) { + } catch (error: unknown) { // eslint-disable-next-line @typescript-eslint/no-floating-promises NotificationMiddleware.onError( NotificationMiddlewareContext.SLIM, @@ -948,7 +910,7 @@ class SlideViewer extends React.Component { if (segmentations.length > 0) { try { this.volumeViewer.addSegments(segmentations) - } catch (error: any) { + } catch (error: unknown) { // eslint-disable-next-line @typescript-eslint/no-floating-promises NotificationMiddleware.onError( NotificationMiddlewareContext.SLIM, @@ -1039,7 +1001,7 @@ class SlideViewer extends React.Component { if (parametricMaps.length > 0) { try { this.volumeViewer.addParameterMappings(parametricMaps) - } catch (error: any) { + } catch (error: unknown) { // eslint-disable-next-line @typescript-eslint/no-floating-promises NotificationMiddleware.onError( NotificationMiddlewareContext.SLIM, @@ -1392,7 +1354,7 @@ class SlideViewer extends React.Component { this.resetUnselectedRoiStyles(updatedSelectedRois) } - handleAnnotationSelection (uid: string): void { + handleAnnotationSelection = (uid: string): void => { // @ts-expect-error this.volumeViewer.clearSelections() @@ -1548,7 +1510,7 @@ class SlideViewer extends React.Component { console.debug(`removed ROI "${roi.uid}"`) } - componentCleanup (): void { + componentCleanup = (): void => { document.body.removeEventListener( 'dicommicroscopyviewer_roi_drawn', this.onRoiDrawn @@ -1664,7 +1626,7 @@ class SlideViewer extends React.Component { } } - componentWillUnmount (): void { + componentWillUnmount = (): void => { this.volumeViewer.cleanup() if (this.labelViewer !== null && this.labelViewer !== undefined) { this.labelViewer.cleanup() @@ -1672,7 +1634,7 @@ class SlideViewer extends React.Component { window.removeEventListener('beforeunload', this.componentCleanup) } - componentSetup (): void { + componentSetup = (): void => { document.body.addEventListener( 'dicommicroscopyviewer_roi_drawn', this.onRoiDrawn @@ -1737,7 +1699,7 @@ class SlideViewer extends React.Component { window.addEventListener('resize', this.onWindowResize) } - componentDidMount (): void { + componentDidMount = (): void => { this.componentSetup() this.populateViewports() @@ -1791,7 +1753,7 @@ class SlideViewer extends React.Component { * @param value - Code value of the coded finding that got selected * @param option - Option that got selected */ - handleAnnotationGeometryTypeSelection (value: string, option: any): void { + handleAnnotationGeometryTypeSelection = (value: string, option: any): void => { this.setState({ selectedGeometryType: value }) } @@ -1799,7 +1761,7 @@ class SlideViewer extends React.Component { * Handler that gets called when measurements have been selected for * annotation. */ - handleAnnotationMeasurementActivation (event: any): void { + handleAnnotationMeasurementActivation = (event: any): void => { const active: boolean = event.target.checked if (active) { this.setState({ selectedMarkup: 'measurement' }) @@ -1850,7 +1812,7 @@ class SlideViewer extends React.Component { * Handler that gets called when an evaluation has been cleared for an * annotation. */ - handleAnnotationEvaluationClearance (): void { + handleAnnotationEvaluationClearance = (): void => { this.setState({ selectedEvaluations: [] }) @@ -1859,10 +1821,12 @@ class SlideViewer extends React.Component { handleXCoordinateSelection = (value: number | string | null): void => { if (value !== null && value !== undefined) { const x = Number(value) - const isValid = x >= this.state.validXCoordinateRange[0] && x <= this.state.validXCoordinateRange[1] - this.setState({ - selectedXCoordinate: x, - isSelectedXCoordinateValid: isValid + this.setState(state => { + const isValid = x >= state.validXCoordinateRange[0] && x <= state.validXCoordinateRange[1] + return { + selectedXCoordinate: x, + isSelectedXCoordinateValid: isValid + } }) } else { this.setState({ @@ -1875,10 +1839,12 @@ class SlideViewer extends React.Component { handleYCoordinateSelection = (value: number | string | null): void => { if (value !== null && value !== undefined) { const y = Number(value) - const isValid = y >= this.state.validYCoordinateRange[0] && y <= this.state.validYCoordinateRange[1] - this.setState({ - selectedYCoordinate: y, - isSelectedYCoordinateValid: isValid + this.setState(state => { + const isValid = y >= state.validYCoordinateRange[0] && y <= state.validYCoordinateRange[1] + return { + selectedYCoordinate: y, + isSelectedYCoordinateValid: isValid + } }) } else { this.setState({ @@ -1891,10 +1857,12 @@ class SlideViewer extends React.Component { handleMagnificationSelection = (value: number | string | null): void => { if (value !== null && value !== undefined) { const magnification = Number(value) - const isValid = magnification >= 0 && magnification <= 40 - this.setState({ - selectedMagnification: magnification, - isSelectedMagnificationValid: isValid + this.setState(() => { + const isValid = magnification >= 0 && magnification <= 40 + return { + selectedMagnification: magnification, + isSelectedMagnificationValid: isValid + } }) } else { this.setState({ @@ -2020,7 +1988,7 @@ class SlideViewer extends React.Component { * Handler that gets called when a report should be generated for the current * set of annotations. */ - handleReportGeneration (): void { + handleReportGeneration = (): void => { console.info('save ROIs') const rois = this.volumeViewer.getAllROIs() const opticalPaths = this.volumeViewer.getAllOpticalPaths() @@ -2047,14 +2015,14 @@ class SlideViewer extends React.Component { * list of annotations will be presented to the user together with other * pertinent metadata about the patient, study, and specimen. */ - handleReportVerification (): void { + handleReportVerification = (): void => { console.info('verify report generation') if (this.state.generatedReport !== undefined) { const client = this.props.clients[StorageClasses.COMPREHENSIVE_3D_SR] // The Comprehensive3DSR object should have a write method or similar // For now, let's try to access it as an ArrayBuffer directly - client.storeInstances({ datasets: [this.state.generatedReport as any] }).then( - (response: any) => message.info('Annotations were saved.') + client.storeInstances({ datasets: [(this.state.generatedReport as unknown as dcmjs.data.DicomDict).write()] }).then( + () => message.info('Annotations were saved.') ).catch((error) => { console.error(error) // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -2076,7 +2044,7 @@ class SlideViewer extends React.Component { /** * Handler that gets called when report generation has been cancelled. */ - handleReportCancellation (): void { + handleReportCancellation = (): void => { this.setState({ isReportModalVisible: false, generatedReport: undefined @@ -2403,7 +2371,7 @@ class SlideViewer extends React.Component { * Set default presentation state that is either defined by metadata included * in the DICOM Slide Microscopy instance or by the viewer. */ - setDefaultPresentationState (): void { + setDefaultPresentationState = (): void => { const visibleOpticalPathIdentifiers: Set = new Set() const opticalPaths = this.volumeViewer.getAllOpticalPaths() opticalPaths.sort((a, b) => { @@ -2481,7 +2449,7 @@ class SlideViewer extends React.Component { * Handler that gets called when a presentation state has been selected from * the current list of available presentation states. */ - handlePresentationStateReset (): void { + handlePresentationStateReset = (): void => { this.setState({ selectedPresentationStateUID: undefined }) const urlPath = this.props.location.pathname this.props.navigate(urlPath) @@ -2533,7 +2501,7 @@ class SlideViewer extends React.Component { * Handler that will toggle the ROI drawing tool, i.e., either activate or * de-activate it, depending on its current state. */ - handleRoiDrawing (): void { + handleRoiDrawing = (): void => { if (this.state.isRoiDrawingActive) { console.info('deactivate drawing of ROIs') this.volumeViewer.deactivateDrawInteraction() @@ -2567,7 +2535,7 @@ class SlideViewer extends React.Component { * Handler that will toggle the ROI modification tool, i.e., either activate * or de-activate it, depending on its current state. */ - handleRoiModification (): void { + handleRoiModification = (): void => { console.info('toggle modification of ROIs') if (this.volumeViewer.isModifyInteractionActive) { this.volumeViewer.deactivateModifyInteraction() @@ -2596,7 +2564,7 @@ class SlideViewer extends React.Component { * Handler that will toggle the ROI translation tool, i.e., either activate * or de-activate it, depending on its current state. */ - handleRoiTranslation (): void { + handleRoiTranslation = (): void => { console.info('toggle translation of ROIs') if (this.volumeViewer.isTranslateInteractionActive) { this.volumeViewer.deactivateTranslateInteraction() @@ -2619,7 +2587,7 @@ class SlideViewer extends React.Component { } } - handleGoTo (): void { + handleGoTo = (): void => { this.volumeViewer.deactivateDrawInteraction() this.volumeViewer.deactivateModifyInteraction() this.volumeViewer.deactivateSnapInteraction() @@ -2640,7 +2608,7 @@ class SlideViewer extends React.Component { * Handler that will toggle the ROI removal tool, i.e., either activate * or de-activate it, depending on its current state. */ - handleRoiRemoval (): void { + handleRoiRemoval = (): void => { this.volumeViewer.deactivateDrawInteraction() this.volumeViewer.deactivateSnapInteraction() this.volumeViewer.deactivateTranslateInteraction() @@ -2682,7 +2650,7 @@ class SlideViewer extends React.Component { * Handler that will toggle the ROI visibility tool, i.e., either activate * or de-activate it, depending on its current state. */ - handleRoiVisibilityChange (): void { + handleRoiVisibilityChange = (): void => { console.info('toggle visibility of ROIs') if (!this.state.areRoisHidden) { this.volumeViewer.deactivateDrawInteraction() @@ -3445,7 +3413,7 @@ class SlideViewer extends React.Component { ) } - render (): React.ReactNode { + render = (): React.ReactNode => { const { rois, segments, mappings, annotationGroups, annotations } = this.getDataFromViewer() const openSubMenuItems = SlideViewer.getOpenSubMenuItems() @@ -3476,7 +3444,7 @@ class SlideViewer extends React.Component { } // Format annotations - annotations?.forEach?.(this.formatAnnotation.bind(this)) + annotations?.forEach?.(this.formatAnnotation) return ( diff --git a/src/components/SlideViewer/GoToModal.tsx b/src/components/SlideViewer/GoToModal.tsx index 58e3bc2a..0042224b 100644 --- a/src/components/SlideViewer/GoToModal.tsx +++ b/src/components/SlideViewer/GoToModal.tsx @@ -32,17 +32,17 @@ const GoToModal: React.FC = ({ onYCoordinateSelection, onMagnificationSelection }) => { - const handleXCoordinateEnter = (event: React.KeyboardEvent): void => { + function handleXCoordinateEnter (event: React.KeyboardEvent): void { const target = event.target as HTMLInputElement onXCoordinateSelection(target.value !== '' ? Number(target.value) : null) } - const handleYCoordinateEnter = (event: React.KeyboardEvent): void => { + function handleYCoordinateEnter (event: React.KeyboardEvent): void { const target = event.target as HTMLInputElement onYCoordinateSelection(target.value !== '' ? Number(target.value) : null) } - const handleMagnificationEnter = (event: React.KeyboardEvent): void => { + function handleMagnificationEnter (event: React.KeyboardEvent): void { const target = event.target as HTMLInputElement onMagnificationSelection(target.value !== '' ? Number(target.value) : null) } diff --git a/src/components/SlideViewer/SlideViewerSidebar.tsx b/src/components/SlideViewer/SlideViewerSidebar.tsx index ead735bb..3738342a 100644 --- a/src/components/SlideViewer/SlideViewerSidebar.tsx +++ b/src/components/SlideViewer/SlideViewerSidebar.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Layout, Menu } from 'antd' +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import AnnotationCategoryList from '../AnnotationCategoryList' import { AnnotationCategoryAndType } from '../../types/annotations' @@ -75,7 +76,7 @@ const SlideViewerSidebar: React.FC = ({ forceSubMenuRender onOpenChange={handleMenuOpenChange} > - {labelViewportRef.current != null && ( + {labelViewportRef.current !== null && (
{ constructor (props: WorklistProps) { super(props) - this.fetchData = this.fetchData.bind(this) - this.handleClick = this.handleClick.bind(this) - this.handleChange = this.handleChange.bind(this) this.state = { studies: [], isLoading: false, @@ -81,15 +79,15 @@ class Worklist extends React.Component { } } - handleClick (event: React.SyntheticEvent, study: dmv.metadata.Study): void { + handleClick = (event: React.SyntheticEvent, study: dmv.metadata.Study): void => { this.props.navigate(`/studies/${study.StudyInstanceUID}`) } - fetchData ({ offset, limit, searchCriteria }: { + fetchData = ({ offset, limit, searchCriteria }: { offset: number limit: number searchCriteria?: { [attribute: string]: string } - }): void { + }): void => { const queryParams: { [key: string]: any } = { ModalitiesInStudy: 'SM', offset: offset, @@ -130,10 +128,10 @@ class Worklist extends React.Component { }) } - handleChange ( + handleChange = ( pagination: TablePaginationConfig, filters: any - ): void { + ): void => { this.setState({ isLoading: true }) let index = pagination.current if (index === undefined) { @@ -168,6 +166,26 @@ class Worklist extends React.Component { clearFilters() } + getRowKey = (record: dmv.metadata.Study): string => { + return record.StudyInstanceUID + } + + handleRowProps = (record: dmv.metadata.Study): object => { + return { + onClick: (event: React.SyntheticEvent): void => { + return this.handleClick(event, record) + } + } + } + + handlePressEnter = (selectedKeys: React.Key[], confirm: (params?: FilterConfirmProps) => void, dataIndex: string): void => { + this.handleSearch(selectedKeys, confirm, dataIndex) + } + + handleInputChange = (e: React.ChangeEvent, setSelectedKeys: (selectedKeys: React.Key[]) => void): void => { + setSelectedKeys(e.target.value !== undefined ? [e.target.value] : []) + } + render (): React.ReactNode { const columns: ColumnsType = [ { @@ -249,16 +267,10 @@ class Worklist extends React.Component { style={{ cursor: 'pointer' }} columns={columns} - rowKey={record => record.StudyInstanceUID} + rowKey={this.getRowKey} dataSource={this.state.studies} pagination={pagination} - onRow={(record: dmv.metadata.Study): object => { - return { - onClick: (event: React.SyntheticEvent): void => { - return this.handleClick(event, record) - } - } - }} + onRow={this.handleRowProps} onChange={this.handleChange} size='small' loading={this.state.isLoading} @@ -267,10 +279,6 @@ class Worklist extends React.Component { } getColumnSearchProps = (dataIndex: string): object => { - const handlePressEnter = (selectedKeys: React.Key[], confirm: (params?: FilterConfirmProps) => void): void => { - this.handleSearch(selectedKeys, confirm, dataIndex) - } - return { filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }: { setSelectedKeys: (selectedKeys: React.Key[]) => void @@ -282,10 +290,8 @@ class Worklist extends React.Component { setSelectedKeys( - e.target.value !== undefined ? [e.target.value] : [] - )} - onPressEnter={() => handlePressEnter(selectedKeys, confirm)} + onChange={(e) => this.handleInputChange(e, setSelectedKeys)} + onPressEnter={() => this.handlePressEnter(selectedKeys, confirm, dataIndex)} style={{ width: 188, marginBottom: 8, display: 'block' }} /> diff --git a/src/components/__tests__/Worklist.test.tsx b/src/components/__tests__/Worklist.test.tsx index 8e22b802..d454aaac 100644 --- a/src/components/__tests__/Worklist.test.tsx +++ b/src/components/__tests__/Worklist.test.tsx @@ -1,6 +1,7 @@ import React from 'react' import { BrowserRouter } from 'react-router-dom' import { cleanup, render } from '@testing-library/react' +// skipcq: JS-C1003 import * as dwc from 'dicomweb-client' import DicomWebManager from '../../DicomWebManager' diff --git a/src/data/slides.tsx b/src/data/slides.tsx index 14365e3e..63b4eaa6 100644 --- a/src/data/slides.tsx +++ b/src/data/slides.tsx @@ -1,3 +1,4 @@ +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import { CustomError, errorTypes } from '../utils/CustomError' import NotificationMiddleware, { diff --git a/src/data/specimens.tsx b/src/data/specimens.tsx index 3f719cc8..72bd55e3 100644 --- a/src/data/specimens.tsx +++ b/src/data/specimens.tsx @@ -1,3 +1,4 @@ +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' export const SpecimenPreparationTypes: { diff --git a/src/services/RoiToAnnotationAdapter.ts b/src/services/RoiToAnnotationAdapter.ts index 5a4bbf7b..399dd617 100644 --- a/src/services/RoiToAnnotationAdapter.ts +++ b/src/services/RoiToAnnotationAdapter.ts @@ -1,4 +1,6 @@ +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' import { AnnotationCategoryAndType } from '../components/AnnotationCategoryList' diff --git a/src/services/fetchImageMetadata.ts b/src/services/fetchImageMetadata.ts index e59af78a..03bd96f2 100644 --- a/src/services/fetchImageMetadata.ts +++ b/src/services/fetchImageMetadata.ts @@ -1,3 +1,4 @@ +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' import DicomWebManager from '../DicomWebManager' diff --git a/src/types/annotations.ts b/src/types/annotations.ts index 541d54d4..d6bfbdfe 100644 --- a/src/types/annotations.ts +++ b/src/types/annotations.ts @@ -1,3 +1,4 @@ +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' /** diff --git a/src/utils/sr.tsx b/src/utils/sr.tsx index d22ba23c..3dba426e 100644 --- a/src/utils/sr.tsx +++ b/src/utils/sr.tsx @@ -1,3 +1,4 @@ +// skipcq: JS-C1003 import * as dcmjs from 'dcmjs' /** diff --git a/src/utils/values.ts b/src/utils/values.ts index 15668dbf..a45d2b1b 100644 --- a/src/utils/values.ts +++ b/src/utils/values.ts @@ -1,3 +1,4 @@ +// skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' function parseName (value: dmv.metadata.PersonName|null|undefined): string { diff --git a/types/dicom-microscopy-viewer/index.d.ts b/types/dicom-microscopy-viewer/index.d.ts index 25c778a1..c9b7f333 100644 --- a/types/dicom-microscopy-viewer/index.d.ts +++ b/types/dicom-microscopy-viewer/index.d.ts @@ -1,7 +1,9 @@ declare module 'dicom-microscopy-viewer' { - import * as dwc from 'dicomweb-client' - import * as dcmjs from 'dcmjs' + // skipcq: JS-C1003 +import * as dwc from 'dicomweb-client' +// skipcq: JS-C1003 +import * as dcmjs from 'dcmjs' import { CustomError } from '../../src/utils/CustomError' declare namespace viewer { From 23f6679b4e7b66c0e81b9b55b9806b6849c7b6ef Mon Sep 17 00:00:00 2001 From: Igor Octaviano Date: Tue, 29 Jul 2025 17:19:59 -0300 Subject: [PATCH 09/13] Address deep source issues --- src/components/SlideViewer.tsx | 60 +++++++++---------- .../SlideViewer/SlideViewerSidebar.tsx | 11 ++-- src/components/Worklist.tsx | 24 ++++++-- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/components/SlideViewer.tsx b/src/components/SlideViewer.tsx index 30a28e17..0bca0f81 100644 --- a/src/components/SlideViewer.tsx +++ b/src/components/SlideViewer.tsx @@ -1255,7 +1255,7 @@ class SlideViewer extends React.Component { .filter((roi): roi is dmv.roi.ROI => roi !== undefined) } - isSamePixelAsLast = (event: any): boolean => { + isSamePixelAsLast = (event: MouseEvent): boolean => { return event.clientX === this.lastPixel[0] && event.clientY === this.lastPixel[1] } @@ -1733,7 +1733,7 @@ class SlideViewer extends React.Component { */ handleAnnotationFindingSelection ( value: string, - option: any + _option: { label: React.ReactNode } ): void { this.findingOptions.forEach(finding => { if (finding.CodeValue === value) { @@ -1753,7 +1753,7 @@ class SlideViewer extends React.Component { * @param value - Code value of the coded finding that got selected * @param option - Option that got selected */ - handleAnnotationGeometryTypeSelection = (value: string, option: any): void => { + handleAnnotationGeometryTypeSelection = (value: string, _option: { label: string }): void => { this.setState({ selectedGeometryType: value }) } @@ -1761,7 +1761,7 @@ class SlideViewer extends React.Component { * Handler that gets called when measurements have been selected for * annotation. */ - handleAnnotationMeasurementActivation = (event: any): void => { + handleAnnotationMeasurementActivation = (event: CheckboxChangeEvent): void => { const active: boolean = event.target.checked if (active) { this.setState({ selectedMarkup: 'measurement' }) @@ -1777,10 +1777,10 @@ class SlideViewer extends React.Component { * @param value - Code value of the coded evaluation that got selected * @param option - Option that got selected */ - handleAnnotationEvaluationSelection ( + handleAnnotationEvaluationSelection = ( value: string, - option: any - ): void { + option: { label: dcmjs.sr.coding.CodedConcept } + ): void => { const selectedFinding = this.state.selectedFinding if (selectedFinding !== undefined) { const key = buildKey(selectedFinding) @@ -2055,10 +2055,10 @@ class SlideViewer extends React.Component { * Handle toggling of annotation visibility, i.e., whether a given * annotation should be either displayed or hidden by the viewer. */ - handleAnnotationVisibilityChange ({ roiUID, isVisible }: { + handleAnnotationVisibilityChange = ({ roiUID, isVisible }: { roiUID: string isVisible: boolean - }): void { + }): void => { if (isVisible) { console.info(`show ROI ${roiUID}`) const roi = this.volumeViewer.getROI(roiUID) @@ -2166,8 +2166,8 @@ class SlideViewer extends React.Component { } } - generateRoiStyle ( - styleOptions: StyleOptions): dmv.viewer.ROIStyleOptions { + generateRoiStyle = ( + styleOptions: StyleOptions): dmv.viewer.ROIStyleOptions => { const opacity = styleOptions.opacity ?? DEFAULT_ANNOTATION_OPACITY const strokeColor = styleOptions.color ?? DEFAULT_ANNOTATION_STROKE_COLOR const fillColor = styleOptions.contourOnly ? [0, 0, 0, 0] : strokeColor.map((c) => Math.min(c + 25, 255)) @@ -2179,10 +2179,10 @@ class SlideViewer extends React.Component { return style } - handleRoiStyleChange ({ uid, styleOptions }: { + handleRoiStyleChange = ({ uid, styleOptions }: { uid: string styleOptions: StyleOptions - }): void { + }): void => { console.log(`change style of ROI ${uid}`) try { this.defaultAnnotationStyles[uid] = styleOptions @@ -2210,10 +2210,10 @@ class SlideViewer extends React.Component { * Handle toggling of segment visibility, i.e., whether a given * segment should be either displayed or hidden by the viewer. */ - handleSegmentVisibilityChange ({ segmentUID, isVisible }: { + handleSegmentVisibilityChange = ({ segmentUID, isVisible }: { segmentUID: string isVisible: boolean - }): void { + }): void => { console.log(`change visibility of segment ${segmentUID}`) if (isVisible) { console.info(`show segment ${segmentUID}`) @@ -2237,12 +2237,12 @@ class SlideViewer extends React.Component { /** * Handle change of segment style. */ - handleSegmentStyleChange ({ segmentUID, styleOptions }: { + handleSegmentStyleChange = ({ segmentUID, styleOptions }: { segmentUID: string styleOptions: { opacity?: number } - }): void { + }): void => { console.log(`change style of segment ${segmentUID}`) this.volumeViewer.setSegmentStyle(segmentUID, styleOptions) } @@ -2251,10 +2251,10 @@ class SlideViewer extends React.Component { * Handle toggling of mapping visibility, i.e., whether a given * mapping should be either displayed or hidden by the viewer. */ - handleMappingVisibilityChange ({ mappingUID, isVisible }: { + handleMappingVisibilityChange = ({ mappingUID, isVisible }: { mappingUID: string isVisible: boolean - }): void { + }): void => { console.log(`change visibility of mapping ${mappingUID}`) if (isVisible) { console.info(`show mapping ${mappingUID}`) @@ -2278,12 +2278,12 @@ class SlideViewer extends React.Component { /** * Handle change of mapping style. */ - handleMappingStyleChange ({ mappingUID, styleOptions }: { + handleMappingStyleChange = ({ mappingUID, styleOptions }: { mappingUID: string styleOptions: { opacity?: number } - }): void { + }): void => { console.log(`change style of mapping ${mappingUID}`) this.volumeViewer.setParameterMappingStyle(mappingUID, styleOptions) } @@ -2292,10 +2292,10 @@ class SlideViewer extends React.Component { * Handle toggling of optical path visibility, i.e., whether a given * optical path should be either displayed or hidden by the viewer. */ - handleOpticalPathVisibilityChange ({ opticalPathIdentifier, isVisible }: { + handleOpticalPathVisibilityChange = ({ opticalPathIdentifier, isVisible }: { opticalPathIdentifier: string isVisible: boolean - }): void { + }): void => { console.log(`change visibility of optical path ${opticalPathIdentifier}`) if (isVisible) { console.info(`show optical path ${opticalPathIdentifier}`) @@ -2323,14 +2323,14 @@ class SlideViewer extends React.Component { /** * Handle change of optical path style. */ - handleOpticalPathStyleChange ({ opticalPathIdentifier, styleOptions }: { + handleOpticalPathStyleChange = ({ opticalPathIdentifier, styleOptions }: { opticalPathIdentifier: string styleOptions: { opacity?: number color?: number[] limitValues?: number[] } - }): void { + }): void => { console.log(`change style of optical path ${opticalPathIdentifier}`) this.volumeViewer.setOpticalPathStyle(opticalPathIdentifier, styleOptions) } @@ -2339,10 +2339,10 @@ class SlideViewer extends React.Component { * Handle toggling of optical path activity, i.e., whether a given * optical path should be either added or removed from the viewport. */ - handleOpticalPathActivityChange ({ opticalPathIdentifier, isActive }: { + handleOpticalPathActivityChange = ({ opticalPathIdentifier, isActive }: { opticalPathIdentifier: string isActive: boolean - }): void { + }): void => { console.log(`change activity of optical path ${opticalPathIdentifier}`) if (isActive) { console.info(`activate optical path ${opticalPathIdentifier}`) @@ -2460,10 +2460,10 @@ class SlideViewer extends React.Component { * Handler that gets called when a presentation state has been selected from * the current list of available presentation states. */ - handlePresentationStateSelection ( + handlePresentationStateSelection = ( value?: string, - option?: any - ): void { + _option?: any + ): void => { if (value !== null) { console.info(`select Presentation State instance "${value ?? 'undefined'}"`) let presentationState diff --git a/src/components/SlideViewer/SlideViewerSidebar.tsx b/src/components/SlideViewer/SlideViewerSidebar.tsx index 3738342a..75fea161 100644 --- a/src/components/SlideViewer/SlideViewerSidebar.tsx +++ b/src/components/SlideViewer/SlideViewerSidebar.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useCallback } from 'react' import { Layout, Menu } from 'antd' // skipcq: JS-C1003 import * as dmv from 'dicom-microscopy-viewer' @@ -48,14 +48,15 @@ const SlideViewerSidebar: React.FC = ({ onRoiStyleChange, defaultAnnotationStyles }) => { - const handleMenuOpenChange = (): void => { + const handleMenuOpenChange = useCallback((): void => { // Give menu item time to render before updating viewer size - setTimeout(() => { + const resizeViewer = (): void => { if (labelViewer !== null && labelViewer !== undefined) { labelViewer.resize() } - }, 100) - } + } + setTimeout(resizeViewer, 100) + }, [labelViewer]) return ( { setSelectedKeys(e.target.value !== undefined ? [e.target.value] : []) } + getFilterInputChangeHandler = (setSelectedKeys: (selectedKeys: React.Key[]) => void) => { + return (e: React.ChangeEvent) => this.handleInputChange(e, setSelectedKeys) + } + + getFilterPressEnterHandler = (selectedKeys: React.Key[], confirm: (params?: FilterConfirmProps) => void, dataIndex: string) => { + return () => this.handlePressEnter(selectedKeys, confirm, dataIndex) + } + + getFilterSearchHandler = (selectedKeys: React.Key[], confirm: (params?: FilterConfirmProps) => void, dataIndex: string) => { + return () => this.handleSearch(selectedKeys, confirm, dataIndex) + } + + getFilterResetHandler = (clearFilters: () => void) => { + return () => this.handleReset(clearFilters) + } + render (): React.ReactNode { const columns: ColumnsType = [ { @@ -290,14 +306,14 @@ class Worklist extends React.Component { this.handleInputChange(e, setSelectedKeys)} - onPressEnter={() => this.handlePressEnter(selectedKeys, confirm, dataIndex)} + onChange={this.getFilterInputChangeHandler(setSelectedKeys)} + onPressEnter={this.getFilterPressEnterHandler(selectedKeys, confirm, dataIndex)} style={{ width: 188, marginBottom: 8, display: 'block' }} />