@gen-epix/ui-client-casedb 4.0.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/AddCasesToEventDialog.js.map +1 -1
- package/dist/_chunks/CaseDbDownloadUtil.js +1 -1
- package/dist/_chunks/CaseDbDownloadUtil.js.map +1 -1
- package/dist/_chunks/CaseInfoDialog.js +4 -4
- package/dist/_chunks/CaseInfoDialog.js.map +1 -1
- package/dist/_chunks/CaseSetInfoDialog.js +4 -4
- package/dist/_chunks/CaseSetInfoDialog.js.map +1 -1
- package/dist/_chunks/CasesAlreadyInCaseSetWarning.js.map +1 -1
- package/dist/_chunks/Dashboard.js +3 -2
- package/dist/_chunks/Dashboard.js.map +1 -1
- package/dist/_chunks/FindSimilarCasesDialog.js +6 -6
- package/dist/_chunks/FindSimilarCasesDialog.js.map +1 -1
- package/dist/_chunks/HomePageTrends.js.map +1 -1
- package/dist/_chunks/LineListWidget.js +46 -45
- package/dist/_chunks/LineListWidget.js.map +1 -1
- package/dist/_chunks/Upload.js +308 -304
- package/dist/_chunks/Upload.js.map +1 -1
- package/dist/_chunks/UploadUtil.js +44 -40
- package/dist/_chunks/UploadUtil.js.map +1 -1
- package/dist/_chunks/dashboardStore.js.map +1 -1
- package/dist/classes/services/EventBusService/EventBusService.d.ts +9 -2
- package/dist/classes/services/EventBusService/index.js.map +1 -1
- package/dist/components/ui/MapWidget/index.js.map +1 -1
- package/dist/components/ui/RemoveCasesFromEventDialog/index.js.map +1 -1
- package/dist/components/ui/SequenceDownloadDialog/index.js +53 -40
- package/dist/components/ui/SequenceDownloadDialog/index.js.map +1 -1
- package/dist/components/ui/TreeWidget/index.js +8 -2
- package/dist/components/ui/TreeWidget/index.js.map +1 -1
- package/dist/locale/en.json +0 -11
- package/dist/locale/nl.json +0 -11
- package/dist/models/config.d.ts +2 -0
- package/dist/utils/UploadUtil/UploadUtil.d.ts +1 -0
- package/package.json +15 -23
- package/dist/components/ui/FileSelector/FileSelector.d.ts +0 -7
- package/dist/components/ui/FileSelector/index.d.ts +0 -1
- package/dist/components/ui/FileSelector/index.js +0 -206
- package/dist/components/ui/FileSelector/index.js.map +0 -1
- package/dist/utils/FileUtil/FileUtil.d.ts +0 -5
- package/dist/utils/FileUtil/index.d.ts +0 -1
- package/dist/utils/FileUtil/index.js +0 -15
- package/dist/utils/FileUtil/index.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/MapWidget/MapWidget.tsx"],"sourcesContent":["import {\n Box,\n ListItemIcon,\n ListItemText,\n MenuItem,\n useTheme,\n} from '@mui/material';\nimport FilterAltIcon from '@mui/icons-material/FilterAlt';\nimport type { EChartsReactProps } from 'echarts-for-react';\nimport EChartsReact from 'echarts-for-react';\nimport {\n GeoComponent,\n LegendComponent,\n TooltipComponent,\n} from 'echarts/components';\nimport { PieChart } from 'echarts/charts';\nimport {\n dispose,\n getInstanceByDom,\n init,\n use as registerECharts,\n registerMap,\n} from 'echarts/core';\nimport { CanvasRenderer } from 'echarts/renderers';\nimport type { ReactElement } from 'react';\nimport {\n use,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type {\n EChartsOption,\n EChartsType,\n} from 'echarts';\nimport { useTranslation } from 'react-i18next';\nimport intersection from 'lodash/intersection';\nimport round from 'lodash/round';\nimport { useStore } from 'zustand';\nimport type {\n CaseDbCol,\n CaseDbRegion,\n CaseDbRegionSetShape,\n CaseDbTypedUuidSetFilter,\n} from '@gen-epix/api-casedb';\nimport {\n CaseDbDimType,\n CaseDbGeoApi,\n} from '@gen-epix/api-casedb';\nimport type { MenuItemData } from '@gen-epix/ui-core-components/models/nestedMenu';\nimport { QueryClientService } from '@gen-epix/ui-client-common/classes/services/QueryClientService';\nimport { useQueryMemo } from '@gen-epix/ui-client-common/hooks/useQueryMemo';\nimport { useDimensions } from '@gen-epix/ui-core/hooks/useDimensions';\n\nimport { DataService } from '../../../classes/services/DataService';\nimport { EventBusService } from '../../../classes/services/EventBusService';\nimport { DashboardStoreContext } from '../../../stores/dashboardStore';\nimport { CaseTypeUtil } from '../../../utils/CaseTypeUtil';\nimport { DashboardUtil } from '../../../utils/DashboardUtil';\nimport { MapUtil } from '../../../utils/MapUtil';\nimport type { GenEpixPieSeriesOptionEventData } from '../../../utils/MapUtil';\nimport type { ContextMenuConfigWithPosition } from '../ContextMenu';\nimport { ContextMenu } from '../ContextMenu';\nimport { WidgetUnavailable } from '../WidgetUnavailable';\nimport { CaseDbDownloadUtil } from '../../../utils/CaseDbDownloadUtil';\nimport { CASEDB_QUERY_KEY } from '../../../constants/query';\nimport { StratificationUtil } from '../../../utils/StratificationUtil';\nimport { DashboardWidget } from '../Dashboard';\nimport { DASHBOARD_COMPONENT_NAME } from '../../../constants/dashboard';\nimport { DashboardContext } from '../Dashboard/context/DashboardContext';\nimport type { MapWidgetData } from '../../../models/dashboard';\n\nconst echartsCore = {\n dispose,\n getInstanceByDom,\n init,\n};\n\nregisterECharts([GeoComponent, TooltipComponent, LegendComponent, CanvasRenderer, PieChart]);\n\ntype GenEpixEchartsEvent = {\n data: GenEpixPieSeriesOptionEventData;\n event: {\n event: MouseEvent;\n };\n};\n\ntype GeoJSON = { features: unknown[] };\n\nexport const MapWidget = () => {\n const { t } = useTranslation();\n const theme = useTheme();\n const [col, setCol] = useState<CaseDbCol>(null);\n const colLabel = col?.label ?? '';\n const [contextMenuConfig, setContextMenuConfig] = useState<ContextMenuConfigWithPosition | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<EChartsReact>(null);\n const { dimensions: { height, width } } = useDimensions(containerRef);\n\n const dashboardContext = use(DashboardContext);\n const dashboardStore = use(DashboardStoreContext);\n const stratification = useStore(dashboardStore, (state) => state.stratification);\n const isDataLoading = useStore(dashboardStore, (state) => state.isDataLoading);\n const sortedData = useStore(dashboardStore, (state) => state.sortedData);\n const updateWidgetData = useStore(dashboardStore, (state) => state.updateWidgetData);\n const mapWidgetData = useStore(dashboardStore, (state) => state.getWidgetData<MapWidgetData>(DASHBOARD_COMPONENT_NAME.MAP));\n const setFilterValue = useStore(dashboardStore, (state) => state.setFilterValue);\n const completeCaseType = useStore(dashboardStore, (state) => state.completeCaseType);\n\n const [focussedRegion, setFocussedRegion] = useState<CaseDbRegion>();\n const geoDims = useMemo(() => CaseTypeUtil.getDims(completeCaseType, [CaseDbDimType.GEO]), [completeCaseType]);\n const regionSetShapesFilter = useMemo<CaseDbTypedUuidSetFilter>(() => {\n return {\n invert: false,\n key: 'region_set_id',\n members: CaseTypeUtil.getRegionSetIds(completeCaseType),\n type: 'UUID_SET',\n };\n }, [completeCaseType]);\n\n const { data: regionSetShapes, error: regionSetShapesError, isLoading: isRegionSetShapesLoading } = useQueryMemo({\n queryFn: async ({ signal }) => {\n return (await CaseDbGeoApi.getInstance().regionSetShapesPostQuery(regionSetShapesFilter, null, null, { signal })).data;\n },\n queryKey: QueryClientService.getInstance().getGenericKey(CASEDB_QUERY_KEY.REGION_SET_SHAPES, regionSetShapesFilter),\n retry: false,\n select: (shapes) => Object.fromEntries(shapes.map(regionSetShape => [regionSetShape.region_set_id, regionSetShape])),\n });\n\n useEffect(() => {\n if (col) {\n return;\n }\n if (!geoDims.length) {\n throw Error('Epi map can not be shown');\n }\n let preferredCol: CaseDbCol;\n if (mapWidgetData.columnId) {\n preferredCol = CaseTypeUtil.getCols(completeCaseType).find(c => c.id === mapWidgetData.columnId);\n } else {\n preferredCol = CaseTypeUtil.getPreferredGEOCol(CaseTypeUtil.getCols(completeCaseType, geoDims[0].id));\n }\n setCol(preferredCol);\n }, [col, completeCaseType, mapWidgetData.columnId, geoDims]);\n\n const regions = useMemo(() => {\n if (!col) {\n return [];\n }\n const regionSetId = completeCaseType.ref_cols[col.ref_col_id].region_set_id;\n return DataService.getInstance().data.regionsByRegionSetId[regionSetId];\n }, [col, completeCaseType.ref_cols]);\n\n const lineListCaseCount = useMemo(() => {\n return DashboardUtil.getCaseCount(sortedData);\n }, [sortedData]);\n\n const onEpiContextMenuClose = useCallback(() => {\n setContextMenuConfig(null);\n setFocussedRegion(null);\n }, []);\n\n const isLoading = isRegionSetShapesLoading || isDataLoading;\n const error = regionSetShapesError;\n\n const regionSetShape = useMemo<CaseDbRegionSetShape>(() => {\n if (!col || !regionSetShapes) {\n return null;\n }\n const regionSetId = completeCaseType.ref_cols[col.ref_col_id].region_set_id;\n return regionSetShapes[regionSetId];\n }, [col, completeCaseType.ref_cols, regionSetShapes]);\n\n const numZones = useMemo(() => {\n if (!regionSetShape) {\n return 0;\n }\n try {\n return (JSON.parse(regionSetShape.geo_json) as GeoJSON).features.length;\n } catch {\n return 0;\n }\n }, [regionSetShape]);\n\n const maxPieChartArea = useMemo(() => MapUtil.getMaxPieChartArea(width, height, numZones), [height, numZones, width]);\n\n const lineListRegionStatistics = useMemo(() => MapUtil.getRegionStatistics(sortedData, col?.id, regions), [col, sortedData, regions]);\n const getPieChartRadius = useCallback((numCases: number): number => {\n return MapUtil.getPieChartRadius(numCases, maxPieChartArea, lineListRegionStatistics);\n }, [lineListRegionStatistics, maxPieChartArea]);\n\n const { mapCaseCount, series } = useMemo(\n () => MapUtil.getMapSeries(col, regions, sortedData, stratification, getPieChartRadius),\n [col, getPieChartRadius, sortedData, regions, stratification],\n );\n\n const getOptions = useCallback(() => {\n if (!regionSetShape) {\n return;\n }\n\n registerMap(regionSetShape.id, regionSetShape.geo_json);\n const aspectScale = MapUtil.getGeoJsonAspectScale(regionSetShape.geo_json);\n\n return {\n color: StratificationUtil.getEchartsColors(stratification, theme),\n geo: {\n aspectScale,\n emphasis: {\n disabled: true,\n },\n itemStyle: {\n areaColor: '#e7e8ea',\n },\n label: {\n show: false,\n silent: true,\n },\n map: regionSetShape.id,\n roam: true,\n scaleLimit: {\n max: 5,\n min: 1,\n },\n silent: true,\n zoom: 1,\n },\n legend: {\n show: false,\n },\n series: series as unknown,\n tooltip: {},\n } satisfies EChartsOption;\n }, [regionSetShape, series, stratification, theme]);\n\n const onChartReady = useCallback((chart: EChartsType) => {\n const dom = chart.getDom();\n dom?.setAttribute('aria-label', t('Figure of a map showing the geographical distribution of cases per region ({{label}})', { label: colLabel }));\n dom?.setAttribute('role', 'img');\n }, [colLabel, t]);\n\n const onEvents = useMemo<EChartsReactProps['onEvents']>(() => {\n return {\n mouseout: () => {\n dashboardContext.highlight({\n caseIds: [],\n origin: DASHBOARD_COMPONENT_NAME.MAP,\n });\n },\n mouseover: (event: unknown) => {\n try {\n dashboardContext.highlight({\n caseIds: (event as GenEpixEchartsEvent).data.genEpixData.caseIds,\n origin: DASHBOARD_COMPONENT_NAME.MAP,\n });\n } catch (_error) {\n // ignore\n }\n },\n mouseup: (event: unknown) => {\n const mouseEvent = (event as GenEpixEchartsEvent)?.event?.event;\n const eventCaseIds = (event as GenEpixEchartsEvent).data.genEpixData.caseIds;\n const region = regions.find(r => r.id === (event as GenEpixEchartsEvent).data.genEpixData.regionId);\n setContextMenuConfig({\n caseIds: eventCaseIds,\n mouseEvent,\n position: {\n left: (event as { event: { event: MouseEvent } }).event.event.clientX,\n top: (event as { event: { event: MouseEvent } }).event.event.clientY,\n },\n });\n setFocussedRegion(region);\n },\n };\n }, [dashboardContext, regions]);\n\n useEffect(() => {\n const unsubscribe = dashboardContext.highlightSubject.subscribe((highlighting) => {\n const foundSerieIndexes: number[] = [];\n const foundDataIndexes: number[] = [];\n series.forEach((serie, serieIndex) => {\n serie.data.forEach((dataArray, dataIndex) => {\n const caseIds = (dataArray as GenEpixPieSeriesOptionEventData).genEpixData.caseIds;\n if (intersection(caseIds, highlighting.caseIds).length) {\n if (!foundSerieIndexes.includes(serieIndex)) {\n foundSerieIndexes.push(serieIndex);\n }\n foundDataIndexes.push(dataIndex);\n }\n });\n });\n if (!chartRef?.current) {\n return;\n }\n\n if (highlighting.caseIds.length) {\n chartRef.current.getEchartsInstance()?.dispatchAction({\n dataIndex: foundDataIndexes,\n seriesIndex: foundSerieIndexes,\n type: 'highlight',\n });\n } else {\n chartRef.current.getEchartsInstance()?.dispatchAction({\n type: 'downplay',\n });\n }\n });\n\n // use highlighting imperatively\n return () => {\n unsubscribe();\n };\n }, [chartRef, dashboardContext.highlightSubject, series]);\n\n const titleMenu = useMemo<MenuItemData>(() => {\n const menu: MenuItemData = {\n disabled: !geoDims?.length,\n items: [],\n label: col?.label ? t('Map: {{label}}', { label: col.label }) : t`Map`,\n tooltip: col ? completeCaseType.ref_cols[col.ref_col_id].description : undefined,\n };\n\n completeCaseType.ordered_dim_ids.map(x => completeCaseType.dims[x]).filter(dim => {\n const refDim = completeCaseType.ref_dims[dim.ref_dim_id];\n return refDim.dim_type === CaseDbDimType.GEO;\n }).forEach((dim) => {\n if (menu.items.length) {\n menu.items.at(-1).divider = true;\n }\n completeCaseType.ordered_col_ids_by_dim[dim.id].map(id => completeCaseType.cols[id]).forEach((c) => {\n menu.items.push({\n active: !!(c.id === col?.id),\n callback: () => {\n updateWidgetData<MapWidgetData>(DASHBOARD_COMPONENT_NAME.MAP, { columnId: c.id });\n setCol(c);\n },\n label: c.label,\n tooltip: c.description,\n });\n });\n });\n\n return menu;\n }, [col, completeCaseType, geoDims.length, t, updateWidgetData]);\n\n const missingCasesCount = mapCaseCount !== undefined ? lineListCaseCount - mapCaseCount : 0;\n const missingCasesPercentage = missingCasesCount > 0 ? round(missingCasesCount / lineListCaseCount * 100, 1) : 0;\n const shouldShowLoading = isLoading && !error;\n const canShowWidget = !!col && (regionSetShape?.geo_json && regionSetShape?.geo_json !== 'null') && series.length > 0;\n\n const widgetUnavailableReason = useMemo<ReactElement | string>(() => {\n if (canShowWidget) {\n return;\n }\n if (!col) {\n return t`No geographical column found.`;\n }\n if (!regionSetShape?.geo_json || regionSetShape?.geo_json === 'null') {\n return t`No geographical shape found for the selected column.`;\n }\n if (!series.length) {\n return t`No cases with valid geographical information found for the selected column.`;\n }\n return null;\n }, [canShowWidget, col, regionSetShape?.geo_json, series.length, t]);\n\n const onShowOnlySelectedRegionMenuItemClick = useCallback(async (onMenuClose: () => void) => {\n await setFilterValue(col.id, [focussedRegion.id]);\n onMenuClose();\n }, [col, focussedRegion, setFilterValue]);\n\n const getEpiContextMenuExtraItems = useCallback((onMenuClose: () => void): ReactElement => {\n if (!col || !focussedRegion?.name) {\n return null;\n }\n return (\n <MenuItem\n divider\n // eslint-disable-next-line @eslint-react/kit/jsx-no-bind\n onClick={async () => await onShowOnlySelectedRegionMenuItemClick(onMenuClose)}\n >\n <ListItemIcon>\n <FilterAltIcon fontSize={'small'} />\n </ListItemIcon>\n <ListItemText>\n {t('Filter (show only {{regionName}})', { regionName: focussedRegion?.name ?? '-' })}\n </ListItemText>\n </MenuItem>\n );\n }, [col, focussedRegion, onShowOnlySelectedRegionMenuItemClick, t]);\n\n useEffect(() => {\n const emitDownloadOptions = () => {\n EventBusService.getInstance().emit('onDownloadOptionsChanged', {\n disabled: !canShowWidget,\n items: [\n {\n callback: () => CaseDbDownloadUtil.downloadEchartsImage(t`Incidence map`, chartRef.current.getEchartsInstance(), 'png', completeCaseType, t),\n label: t`Save as PNG`,\n },\n {\n callback: () => CaseDbDownloadUtil.downloadEchartsImage(t`Incidence map`, chartRef.current.getEchartsInstance(), 'jpeg', completeCaseType, t),\n label: t`Save as JPEG`,\n },\n ],\n zone: DASHBOARD_COMPONENT_NAME.MAP,\n zoneLabel: t`Map`,\n });\n };\n\n\n emitDownloadOptions();\n const eventBusService = EventBusService.getInstance();\n eventBusService.addEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n return () => {\n EventBusService.getInstance().emit('onDownloadOptionsChanged', {\n items: null,\n zone: DASHBOARD_COMPONENT_NAME.MAP,\n zoneLabel: t`Map`,\n });\n eventBusService.removeEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n };\n }, [completeCaseType, canShowWidget, t]);\n\n return (\n <DashboardWidget\n expandDisabled={!canShowWidget}\n isLoading={shouldShowLoading}\n title={titleMenu}\n warningMessage={canShowWidget && mapCaseCount > 0 && missingCasesCount > 0 ? t('Missing cases: {{missingCasesCount}} ({{missingCasesPercentage}}%)', { missingCasesCount, missingCasesPercentage }) : undefined}\n widgetName={DASHBOARD_COMPONENT_NAME.MAP}\n >\n <Box\n ref={containerRef}\n sx={{\n height: '100%',\n position: 'relative',\n }}\n >\n {!shouldShowLoading && !canShowWidget && (\n <WidgetUnavailable\n reason={widgetUnavailableReason}\n widgetLabel={t`map`}\n />\n )}\n {canShowWidget && (\n <EChartsReact\n echarts={echartsCore}\n notMerge\n onChartReady={onChartReady}\n onEvents={onEvents}\n option={getOptions()}\n ref={chartRef}\n style={{\n height: '100%',\n position: 'absolute',\n width: '100%',\n }}\n />\n )}\n <ContextMenu\n config={contextMenuConfig}\n getExtraItems={getEpiContextMenuExtraItems}\n onMenuClose={onEpiContextMenuClose}\n />\n </Box>\n </DashboardWidget>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/MapWidget/MapWidget.tsx"],"sourcesContent":["import {\n Box,\n ListItemIcon,\n ListItemText,\n MenuItem,\n useTheme,\n} from '@mui/material';\nimport FilterAltIcon from '@mui/icons-material/FilterAlt';\nimport type { EChartsReactProps } from 'echarts-for-react';\nimport EChartsReact from 'echarts-for-react';\nimport {\n GeoComponent,\n LegendComponent,\n TooltipComponent,\n} from 'echarts/components';\nimport { PieChart } from 'echarts/charts';\nimport {\n dispose,\n getInstanceByDom,\n init,\n use as registerECharts,\n registerMap,\n} from 'echarts/core';\nimport { CanvasRenderer } from 'echarts/renderers';\nimport type { ReactElement } from 'react';\nimport {\n use,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type {\n EChartsOption,\n EChartsType,\n} from 'echarts';\nimport { useTranslation } from 'react-i18next';\nimport intersection from 'lodash/intersection';\nimport round from 'lodash/round';\nimport { useStore } from 'zustand';\nimport type {\n CaseDbCol,\n CaseDbRegion,\n CaseDbRegionSetShape,\n CaseDbUuidSetFilter,\n} from '@gen-epix/api-casedb';\nimport {\n CaseDbDimType,\n CaseDbGeoApi,\n} from '@gen-epix/api-casedb';\nimport type { MenuItemData } from '@gen-epix/ui-core-components/models/nestedMenu';\nimport { QueryClientService } from '@gen-epix/ui-client-common/classes/services/QueryClientService';\nimport { useQueryMemo } from '@gen-epix/ui-client-common/hooks/useQueryMemo';\nimport { useDimensions } from '@gen-epix/ui-core/hooks/useDimensions';\n\nimport { DataService } from '../../../classes/services/DataService';\nimport { EventBusService } from '../../../classes/services/EventBusService';\nimport { DashboardStoreContext } from '../../../stores/dashboardStore';\nimport { CaseTypeUtil } from '../../../utils/CaseTypeUtil';\nimport { DashboardUtil } from '../../../utils/DashboardUtil';\nimport { MapUtil } from '../../../utils/MapUtil';\nimport type { GenEpixPieSeriesOptionEventData } from '../../../utils/MapUtil';\nimport type { ContextMenuConfigWithPosition } from '../ContextMenu';\nimport { ContextMenu } from '../ContextMenu';\nimport { WidgetUnavailable } from '../WidgetUnavailable';\nimport { CaseDbDownloadUtil } from '../../../utils/CaseDbDownloadUtil';\nimport { CASEDB_QUERY_KEY } from '../../../constants/query';\nimport { StratificationUtil } from '../../../utils/StratificationUtil';\nimport { DashboardWidget } from '../Dashboard';\nimport { DASHBOARD_COMPONENT_NAME } from '../../../constants/dashboard';\nimport { DashboardContext } from '../Dashboard/context/DashboardContext';\nimport type { MapWidgetData } from '../../../models/dashboard';\n\nconst echartsCore = {\n dispose,\n getInstanceByDom,\n init,\n};\n\nregisterECharts([GeoComponent, TooltipComponent, LegendComponent, CanvasRenderer, PieChart]);\n\ntype GenEpixEchartsEvent = {\n data: GenEpixPieSeriesOptionEventData;\n event: {\n event: MouseEvent;\n };\n};\n\ntype GeoJSON = { features: unknown[] };\n\nexport const MapWidget = () => {\n const { t } = useTranslation();\n const theme = useTheme();\n const [col, setCol] = useState<CaseDbCol>(null);\n const colLabel = col?.label ?? '';\n const [contextMenuConfig, setContextMenuConfig] = useState<ContextMenuConfigWithPosition | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<EChartsReact>(null);\n const { dimensions: { height, width } } = useDimensions(containerRef);\n\n const dashboardContext = use(DashboardContext);\n const dashboardStore = use(DashboardStoreContext);\n const stratification = useStore(dashboardStore, (state) => state.stratification);\n const isDataLoading = useStore(dashboardStore, (state) => state.isDataLoading);\n const sortedData = useStore(dashboardStore, (state) => state.sortedData);\n const updateWidgetData = useStore(dashboardStore, (state) => state.updateWidgetData);\n const mapWidgetData = useStore(dashboardStore, (state) => state.getWidgetData<MapWidgetData>(DASHBOARD_COMPONENT_NAME.MAP));\n const setFilterValue = useStore(dashboardStore, (state) => state.setFilterValue);\n const completeCaseType = useStore(dashboardStore, (state) => state.completeCaseType);\n\n const [focussedRegion, setFocussedRegion] = useState<CaseDbRegion>();\n const geoDims = useMemo(() => CaseTypeUtil.getDims(completeCaseType, [CaseDbDimType.GEO]), [completeCaseType]);\n const regionSetShapesFilter = useMemo<CaseDbUuidSetFilter>(() => {\n return {\n invert: false,\n key: 'region_set_id',\n members: CaseTypeUtil.getRegionSetIds(completeCaseType),\n type: 'UUID_SET',\n };\n }, [completeCaseType]);\n\n const { data: regionSetShapes, error: regionSetShapesError, isLoading: isRegionSetShapesLoading } = useQueryMemo({\n queryFn: async ({ signal }) => {\n return (await CaseDbGeoApi.getInstance().regionSetShapesPostQuery(regionSetShapesFilter, null, null, { signal })).data;\n },\n queryKey: QueryClientService.getInstance().getGenericKey(CASEDB_QUERY_KEY.REGION_SET_SHAPES, regionSetShapesFilter),\n retry: false,\n select: (shapes) => Object.fromEntries(shapes.map(regionSetShape => [regionSetShape.region_set_id, regionSetShape])),\n });\n\n useEffect(() => {\n if (col) {\n return;\n }\n if (!geoDims.length) {\n throw Error('Epi map can not be shown');\n }\n let preferredCol: CaseDbCol;\n if (mapWidgetData.columnId) {\n preferredCol = CaseTypeUtil.getCols(completeCaseType).find(c => c.id === mapWidgetData.columnId);\n } else {\n preferredCol = CaseTypeUtil.getPreferredGEOCol(CaseTypeUtil.getCols(completeCaseType, geoDims[0].id));\n }\n setCol(preferredCol);\n }, [col, completeCaseType, mapWidgetData.columnId, geoDims]);\n\n const regions = useMemo(() => {\n if (!col) {\n return [];\n }\n const regionSetId = completeCaseType.ref_cols[col.ref_col_id].region_set_id;\n return DataService.getInstance().data.regionsByRegionSetId[regionSetId];\n }, [col, completeCaseType.ref_cols]);\n\n const lineListCaseCount = useMemo(() => {\n return DashboardUtil.getCaseCount(sortedData);\n }, [sortedData]);\n\n const onEpiContextMenuClose = useCallback(() => {\n setContextMenuConfig(null);\n setFocussedRegion(null);\n }, []);\n\n const isLoading = isRegionSetShapesLoading || isDataLoading;\n const error = regionSetShapesError;\n\n const regionSetShape = useMemo<CaseDbRegionSetShape>(() => {\n if (!col || !regionSetShapes) {\n return null;\n }\n const regionSetId = completeCaseType.ref_cols[col.ref_col_id].region_set_id;\n return regionSetShapes[regionSetId];\n }, [col, completeCaseType.ref_cols, regionSetShapes]);\n\n const numZones = useMemo(() => {\n if (!regionSetShape) {\n return 0;\n }\n try {\n return (JSON.parse(regionSetShape.geo_json) as GeoJSON).features.length;\n } catch {\n return 0;\n }\n }, [regionSetShape]);\n\n const maxPieChartArea = useMemo(() => MapUtil.getMaxPieChartArea(width, height, numZones), [height, numZones, width]);\n\n const lineListRegionStatistics = useMemo(() => MapUtil.getRegionStatistics(sortedData, col?.id, regions), [col, sortedData, regions]);\n const getPieChartRadius = useCallback((numCases: number): number => {\n return MapUtil.getPieChartRadius(numCases, maxPieChartArea, lineListRegionStatistics);\n }, [lineListRegionStatistics, maxPieChartArea]);\n\n const { mapCaseCount, series } = useMemo(\n () => MapUtil.getMapSeries(col, regions, sortedData, stratification, getPieChartRadius),\n [col, getPieChartRadius, sortedData, regions, stratification],\n );\n\n const getOptions = useCallback(() => {\n if (!regionSetShape) {\n return;\n }\n\n registerMap(regionSetShape.id, regionSetShape.geo_json);\n const aspectScale = MapUtil.getGeoJsonAspectScale(regionSetShape.geo_json);\n\n return {\n color: StratificationUtil.getEchartsColors(stratification, theme),\n geo: {\n aspectScale,\n emphasis: {\n disabled: true,\n },\n itemStyle: {\n areaColor: '#e7e8ea',\n },\n label: {\n show: false,\n silent: true,\n },\n map: regionSetShape.id,\n roam: true,\n scaleLimit: {\n max: 5,\n min: 1,\n },\n silent: true,\n zoom: 1,\n },\n legend: {\n show: false,\n },\n series: series as unknown,\n tooltip: {},\n } satisfies EChartsOption;\n }, [regionSetShape, series, stratification, theme]);\n\n const onChartReady = useCallback((chart: EChartsType) => {\n const dom = chart.getDom();\n dom?.setAttribute('aria-label', t('Figure of a map showing the geographical distribution of cases per region ({{label}})', { label: colLabel }));\n dom?.setAttribute('role', 'img');\n }, [colLabel, t]);\n\n const onEvents = useMemo<EChartsReactProps['onEvents']>(() => {\n return {\n mouseout: () => {\n dashboardContext.highlight({\n caseIds: [],\n origin: DASHBOARD_COMPONENT_NAME.MAP,\n });\n },\n mouseover: (event: unknown) => {\n try {\n dashboardContext.highlight({\n caseIds: (event as GenEpixEchartsEvent).data.genEpixData.caseIds,\n origin: DASHBOARD_COMPONENT_NAME.MAP,\n });\n } catch (_error) {\n // ignore\n }\n },\n mouseup: (event: unknown) => {\n const mouseEvent = (event as GenEpixEchartsEvent)?.event?.event;\n const eventCaseIds = (event as GenEpixEchartsEvent).data.genEpixData.caseIds;\n const region = regions.find(r => r.id === (event as GenEpixEchartsEvent).data.genEpixData.regionId);\n setContextMenuConfig({\n caseIds: eventCaseIds,\n mouseEvent,\n position: {\n left: (event as { event: { event: MouseEvent } }).event.event.clientX,\n top: (event as { event: { event: MouseEvent } }).event.event.clientY,\n },\n });\n setFocussedRegion(region);\n },\n };\n }, [dashboardContext, regions]);\n\n useEffect(() => {\n const unsubscribe = dashboardContext.highlightSubject.subscribe((highlighting) => {\n const foundSerieIndexes: number[] = [];\n const foundDataIndexes: number[] = [];\n series.forEach((serie, serieIndex) => {\n serie.data.forEach((dataArray, dataIndex) => {\n const caseIds = (dataArray as GenEpixPieSeriesOptionEventData).genEpixData.caseIds;\n if (intersection(caseIds, highlighting.caseIds).length) {\n if (!foundSerieIndexes.includes(serieIndex)) {\n foundSerieIndexes.push(serieIndex);\n }\n foundDataIndexes.push(dataIndex);\n }\n });\n });\n if (!chartRef?.current) {\n return;\n }\n\n if (highlighting.caseIds.length) {\n chartRef.current.getEchartsInstance()?.dispatchAction({\n dataIndex: foundDataIndexes,\n seriesIndex: foundSerieIndexes,\n type: 'highlight',\n });\n } else {\n chartRef.current.getEchartsInstance()?.dispatchAction({\n type: 'downplay',\n });\n }\n });\n\n // use highlighting imperatively\n return () => {\n unsubscribe();\n };\n }, [chartRef, dashboardContext.highlightSubject, series]);\n\n const titleMenu = useMemo<MenuItemData>(() => {\n const menu: MenuItemData = {\n disabled: !geoDims?.length,\n items: [],\n label: col?.label ? t('Map: {{label}}', { label: col.label }) : t`Map`,\n tooltip: col ? completeCaseType.ref_cols[col.ref_col_id].description : undefined,\n };\n\n completeCaseType.ordered_dim_ids.map(x => completeCaseType.dims[x]).filter(dim => {\n const refDim = completeCaseType.ref_dims[dim.ref_dim_id];\n return refDim.dim_type === CaseDbDimType.GEO;\n }).forEach((dim) => {\n if (menu.items.length) {\n menu.items.at(-1).divider = true;\n }\n completeCaseType.ordered_col_ids_by_dim[dim.id].map(id => completeCaseType.cols[id]).forEach((c) => {\n menu.items.push({\n active: !!(c.id === col?.id),\n callback: () => {\n updateWidgetData<MapWidgetData>(DASHBOARD_COMPONENT_NAME.MAP, { columnId: c.id });\n setCol(c);\n },\n label: c.label,\n tooltip: c.description,\n });\n });\n });\n\n return menu;\n }, [col, completeCaseType, geoDims.length, t, updateWidgetData]);\n\n const missingCasesCount = mapCaseCount !== undefined ? lineListCaseCount - mapCaseCount : 0;\n const missingCasesPercentage = missingCasesCount > 0 ? round(missingCasesCount / lineListCaseCount * 100, 1) : 0;\n const shouldShowLoading = isLoading && !error;\n const canShowWidget = !!col && (regionSetShape?.geo_json && regionSetShape?.geo_json !== 'null') && series.length > 0;\n\n const widgetUnavailableReason = useMemo<ReactElement | string>(() => {\n if (canShowWidget) {\n return;\n }\n if (!col) {\n return t`No geographical column found.`;\n }\n if (!regionSetShape?.geo_json || regionSetShape?.geo_json === 'null') {\n return t`No geographical shape found for the selected column.`;\n }\n if (!series.length) {\n return t`No cases with valid geographical information found for the selected column.`;\n }\n return null;\n }, [canShowWidget, col, regionSetShape?.geo_json, series.length, t]);\n\n const onShowOnlySelectedRegionMenuItemClick = useCallback(async (onMenuClose: () => void) => {\n await setFilterValue(col.id, [focussedRegion.id]);\n onMenuClose();\n }, [col, focussedRegion, setFilterValue]);\n\n const getEpiContextMenuExtraItems = useCallback((onMenuClose: () => void): ReactElement => {\n if (!col || !focussedRegion?.name) {\n return null;\n }\n return (\n <MenuItem\n divider\n // eslint-disable-next-line @eslint-react/kit/jsx-no-bind\n onClick={async () => await onShowOnlySelectedRegionMenuItemClick(onMenuClose)}\n >\n <ListItemIcon>\n <FilterAltIcon fontSize={'small'} />\n </ListItemIcon>\n <ListItemText>\n {t('Filter (show only {{regionName}})', { regionName: focussedRegion?.name ?? '-' })}\n </ListItemText>\n </MenuItem>\n );\n }, [col, focussedRegion, onShowOnlySelectedRegionMenuItemClick, t]);\n\n useEffect(() => {\n const emitDownloadOptions = () => {\n EventBusService.getInstance().emit('onDownloadOptionsChanged', {\n disabled: !canShowWidget,\n items: [\n {\n callback: () => CaseDbDownloadUtil.downloadEchartsImage(t`Incidence map`, chartRef.current.getEchartsInstance(), 'png', completeCaseType, t),\n label: t`Save as PNG`,\n },\n {\n callback: () => CaseDbDownloadUtil.downloadEchartsImage(t`Incidence map`, chartRef.current.getEchartsInstance(), 'jpeg', completeCaseType, t),\n label: t`Save as JPEG`,\n },\n ],\n zone: DASHBOARD_COMPONENT_NAME.MAP,\n zoneLabel: t`Map`,\n });\n };\n\n\n emitDownloadOptions();\n const eventBusService = EventBusService.getInstance();\n eventBusService.addEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n return () => {\n EventBusService.getInstance().emit('onDownloadOptionsChanged', {\n items: null,\n zone: DASHBOARD_COMPONENT_NAME.MAP,\n zoneLabel: t`Map`,\n });\n eventBusService.removeEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n };\n }, [completeCaseType, canShowWidget, t]);\n\n return (\n <DashboardWidget\n expandDisabled={!canShowWidget}\n isLoading={shouldShowLoading}\n title={titleMenu}\n warningMessage={canShowWidget && mapCaseCount > 0 && missingCasesCount > 0 ? t('Missing cases: {{missingCasesCount}} ({{missingCasesPercentage}}%)', { missingCasesCount, missingCasesPercentage }) : undefined}\n widgetName={DASHBOARD_COMPONENT_NAME.MAP}\n >\n <Box\n ref={containerRef}\n sx={{\n height: '100%',\n position: 'relative',\n }}\n >\n {!shouldShowLoading && !canShowWidget && (\n <WidgetUnavailable\n reason={widgetUnavailableReason}\n widgetLabel={t`map`}\n />\n )}\n {canShowWidget && (\n <EChartsReact\n echarts={echartsCore}\n notMerge\n onChartReady={onChartReady}\n onEvents={onEvents}\n option={getOptions()}\n ref={chartRef}\n style={{\n height: '100%',\n position: 'absolute',\n width: '100%',\n }}\n />\n )}\n <ContextMenu\n config={contextMenuConfig}\n getExtraItems={getEpiContextMenuExtraItems}\n onMenuClose={onEpiContextMenuClose}\n />\n </Box>\n </DashboardWidget>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/RemoveCasesFromEventDialog/RemoveCasesFromEventDialog.tsx"],"sourcesContent":["import { Box } from '@mui/material';\nimport {\n use,\n useCallback,\n useEffect,\n useMemo,\n} from 'react';\nimport type { ReactElement } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useStore } from 'zustand';\nimport { useShallow } from 'zustand/shallow';\nimport type {\n CaseDbCase,\n CaseDbCaseSet,\n CaseDbCaseSetMember,\n
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/RemoveCasesFromEventDialog/RemoveCasesFromEventDialog.tsx"],"sourcesContent":["import { Box } from '@mui/material';\nimport {\n use,\n useCallback,\n useEffect,\n useMemo,\n} from 'react';\nimport type { ReactElement } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useStore } from 'zustand';\nimport { useShallow } from 'zustand/shallow';\nimport type {\n CaseDbCase,\n CaseDbCaseSet,\n CaseDbCaseSetMember,\n CaseDbCompositeFilter,\n} from '@gen-epix/api-casedb';\nimport { CaseDbCaseApi } from '@gen-epix/api-casedb';\nimport type { DialogAction } from '@gen-epix/ui-core-components/components/Dialog';\nimport type {\n WithDialogRefMethods,\n WithDialogRenderProps,\n} from '@gen-epix/ui-core-components/hoc/withDialog';\nimport { QueryClientService } from '@gen-epix/ui-client-common/classes/services/QueryClientService';\nimport { ResponseHandler } from '@gen-epix/ui-client-common/components/ui/ResponseHandler';\nimport { Spinner } from '@gen-epix/ui-core-components/components/Spinner';\nimport { useDeleteMutation } from '@gen-epix/ui-client-common/hooks/useDeleteMutation';\nimport { useQueryMemo } from '@gen-epix/ui-client-common/hooks/useQueryMemo';\nimport { withDialog } from '@gen-epix/ui-core-components/hoc/withDialog';\nimport { TestIdUtil } from '@gen-epix/ui-core/utils/TestIdUtil';\n\nimport { DashboardStoreContext } from '../../../stores/dashboardStore';\nimport { CASEDB_QUERY_KEY } from '../../../constants/query';\n\nexport interface RemoveCasesFromEventDialogOpenProps {\n caseSet: CaseDbCaseSet;\n rows: CaseDbCase[];\n}\n\nexport interface RemoveCasesFromEventDialogProps extends WithDialogRenderProps<RemoveCasesFromEventDialogOpenProps> {\n //\n}\n\nexport type RemoveCasesFromEventDialogRefMethods = WithDialogRefMethods<RemoveCasesFromEventDialogProps, RemoveCasesFromEventDialogOpenProps>;\n\nexport const RemoveCasesFromEventDialog = withDialog<RemoveCasesFromEventDialogProps, RemoveCasesFromEventDialogOpenProps>((\n {\n onActionsChange,\n onClose,\n onTitleChange,\n openProps,\n }: RemoveCasesFromEventDialogProps,\n): ReactElement => {\n const { t } = useTranslation();\n\n const dashboardStore = use(DashboardStoreContext);\n const completeCaseType = useStore(dashboardStore, useShallow((state) => state.completeCaseType));\n const fetchData = useStore(dashboardStore, useShallow((state) => state.fetchData));\n const setSelectedIds = useStore(dashboardStore, useShallow((state) => state.setSelectedIds));\n\n const isMaxExceeded = openProps.rows.length > completeCaseType.props.delete_max_n_cases;\n\n const caseSetMembersFilter = useMemo<CaseDbCompositeFilter>(() => ({\n filters: [\n {\n invert: false,\n key: 'case_id',\n members: openProps.rows.map(row => row.id),\n type: 'UUID_SET',\n },\n {\n invert: false,\n key: 'case_set_id',\n members: [openProps.caseSet.id],\n type: 'UUID_SET',\n },\n ],\n operator: 'AND',\n type: 'COMPOSITE',\n }), [openProps.caseSet.id, openProps.rows]);\n\n const { data: caseSetMembers, error: caseSetMembersError, isLoading: isCaseSetMembersLoading } = useQueryMemo({\n queryFn: async ({ signal }) => {\n const response = await CaseDbCaseApi.getInstance().caseSetMembersPostQuery(caseSetMembersFilter, null, null, { signal });\n return response.data;\n },\n queryKey: QueryClientService.getInstance().getGenericKey(CASEDB_QUERY_KEY.CASE_SET_MEMBERS, caseSetMembersFilter),\n });\n\n const onSuccess = useCallback(async () => {\n setSelectedIds([]);\n await fetchData();\n onClose();\n }, [fetchData, onClose, setSelectedIds]);\n\n const onError = useCallback(async () => {\n await fetchData();\n onClose();\n }, [fetchData, onClose]);\n\n const { isMutating, mutate } = useDeleteMutation<CaseDbCaseSetMember[]>({\n associationQueryKeys: QueryClientService.getInstance().getQueryKeyDependencies([CASEDB_QUERY_KEY.CASE_SET_MEMBERS], true),\n getErrorNotificationMessage: () => t('Could not remove all cases from {{eventName}}.', { eventName: openProps.caseSet.name }),\n getProgressNotificationMessage: (items) => t('Removing {{numCases}} case(s) from {{eventName}}...', { eventName: openProps.caseSet.name, numCases: items.length }),\n getSuccessNotificationMessage: (items) => t('Successfully removed {{numCases}} case(s) from {{eventName}}.', { eventName: openProps.caseSet.name, numCases: items.length }),\n onError,\n onSuccess,\n queryFn: async (items: CaseDbCaseSetMember[]) => {\n await CaseDbCaseApi.getInstance().caseSetMembersDeleteSome(items.map(item => item.id).join(','));\n },\n });\n\n const onConfirmButtonClick = useCallback((() => {\n mutate(caseSetMembers);\n }), [mutate, caseSetMembers]);\n\n const onCancelButtonClick = useCallback((() => {\n onClose();\n }), [onClose]);\n\n useEffect(() => {\n onTitleChange(t`Remove selected cases from event`);\n }, [onTitleChange, t]);\n\n\n useEffect(() => {\n const actions: DialogAction[] = [];\n actions.push(\n {\n ...TestIdUtil.createAttributes('RemoveCasesFromEventDialog-closeButton'),\n autoFocus: true,\n color: 'primary',\n disabled: isMutating,\n label: t`Close`,\n onClick: onCancelButtonClick,\n variant: 'outlined',\n },\n );\n if (!isMaxExceeded) {\n actions.push(\n {\n ...TestIdUtil.createAttributes('RemoveCasesFromEventDialog-confirmButton'),\n autoFocus: true,\n color: 'secondary',\n disabled: isMutating,\n label: t`Confirm`,\n onClick: onConfirmButtonClick,\n variant: 'contained',\n },\n );\n }\n onActionsChange(actions);\n }, [isMaxExceeded, isMutating, onActionsChange, onCancelButtonClick, onConfirmButtonClick, t]);\n\n if (isMutating) {\n return <Spinner />;\n }\n\n return (\n <ResponseHandler\n error={caseSetMembersError}\n isLoading={isCaseSetMembersLoading}\n shouldHideActionButtons\n >\n {isMaxExceeded ? (\n <Box>\n {t('You can only remove up to {{maxCases}} cases at a time. Please refine your selection and try again.', { maxCases: completeCaseType.props.delete_max_n_cases })}\n </Box>\n ) : (\n <Box>\n {t('Are you sure you want to remove {{numCases}} selected cases from {{eventName}}?', { eventName: openProps.caseSet.name, numCases: openProps.rows.length })}\n </Box>\n )}\n </ResponseHandler>\n );\n}, {\n defaultTitle: '',\n fullWidth: true,\n maxWidth: 'md',\n testId: 'RemoveCasesFromEventDialog',\n});\n"],"mappings":"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"}
|
|
@@ -1,60 +1,86 @@
|
|
|
1
1
|
import { t as e } from "../../../_chunks/react.js";
|
|
2
2
|
import { t } from "../../../_chunks/dashboardStore.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { EventBusService as n } from "../../../classes/services/EventBusService/index.js";
|
|
4
|
+
import { t as r } from "../../../_chunks/shallow.js";
|
|
5
|
+
import { n as i, o as a } from "../../../_chunks/index.esm.js";
|
|
6
|
+
import { CaseDbColType as o } from "@gen-epix/api-casedb";
|
|
7
|
+
import { StringUtil as s } from "@gen-epix/ui-core/utils/StringUtil";
|
|
8
|
+
import { ConfigService as c } from "@gen-epix/ui-client-common/classes/services/ConfigService";
|
|
9
|
+
import { Box as l } from "@mui/material";
|
|
9
10
|
import { use as u, useCallback as d, useEffect as f, useMemo as p, useState as m } from "react";
|
|
10
11
|
import { useTranslation as h } from "react-i18next";
|
|
11
|
-
import { jsx as g
|
|
12
|
-
import { DownloadUtil as
|
|
13
|
-
import { withDialog as
|
|
14
|
-
import { Autocomplete as
|
|
15
|
-
import
|
|
16
|
-
import { AuthenticationService as
|
|
12
|
+
import { jsx as g } from "react/jsx-runtime";
|
|
13
|
+
import { DownloadUtil as _ } from "@gen-epix/ui-core/utils/DownloadUtil";
|
|
14
|
+
import { withDialog as v } from "@gen-epix/ui-core-components/hoc/withDialog";
|
|
15
|
+
import { Autocomplete as y } from "@gen-epix/ui-core-form/components/fields/Autocomplete";
|
|
16
|
+
import b from "@mui/icons-material/Download";
|
|
17
|
+
import { AuthenticationService as x } from "@gen-epix/ui-client-common/classes/services/AuthenticationService";
|
|
17
18
|
//#region src/components/ui/SequenceDownloadDialog/SequenceDownloadDialog.tsx
|
|
18
|
-
var
|
|
19
|
-
let { t: T } = h(), E = u(t), D = e(E,
|
|
19
|
+
var S = v(({ onActionsChange: v, onClose: S, onTitleChange: C, openProps: w }) => {
|
|
20
|
+
let { t: T } = h(), E = u(t), D = e(E, r((e) => e.completeCaseType)), O = p(() => {
|
|
20
21
|
let e = [];
|
|
21
22
|
return D.ordered_dim_ids.map((e) => D.dims[e]).forEach((t) => {
|
|
22
23
|
D.ordered_col_ids_by_dim[t.id].map((e) => D.cols[e]).forEach((t) => {
|
|
23
|
-
D.ref_cols[t.ref_col_id]?.col_type ===
|
|
24
|
+
D.ref_cols[t.ref_col_id]?.col_type === o.GENETIC_SEQUENCE && e.push({
|
|
24
25
|
label: t.label,
|
|
25
26
|
value: t.id
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
}), e;
|
|
29
|
-
}, [D]), [k, A] = m(w?.geneticSequenceColId ?? O?.length === 1 ? O[0].value : ""), j =
|
|
30
|
-
|
|
31
|
-
accessToken:
|
|
32
|
-
action: `${
|
|
30
|
+
}, [D]), [k, A] = m(w?.geneticSequenceColId ?? O?.length === 1 ? O[0].value : ""), j = a({ values: { geneticSequenceColId: k } }), M = d(() => {}, []), N = d(() => {
|
|
31
|
+
_.downloadAsMultiPartForm({
|
|
32
|
+
accessToken: x.getInstance().authContextProps?.user?.access_token ?? "",
|
|
33
|
+
action: `${c.getInstance().config.getAPIBaseUrl()}/v1/retrieve/genetic_sequence/fasta`,
|
|
33
34
|
data: {
|
|
34
35
|
case_ids: w.cases.map((e) => e.id),
|
|
35
36
|
case_type_id: D.id,
|
|
36
|
-
file_name: `${
|
|
37
|
+
file_name: `${s.createSlug(D.name)}-${s.createSlug(O.find((e) => e.value === k)?.label)}-sequences.fasta`,
|
|
37
38
|
genetic_sequence_col_id: k
|
|
38
39
|
}
|
|
39
|
-
}),
|
|
40
|
+
}), S();
|
|
40
41
|
}, [
|
|
41
42
|
D.id,
|
|
42
43
|
D.name,
|
|
43
44
|
k,
|
|
44
45
|
O,
|
|
45
|
-
|
|
46
|
+
S,
|
|
46
47
|
w.cases
|
|
47
48
|
]);
|
|
48
49
|
return f(() => {
|
|
49
50
|
C(T`Download sequences`);
|
|
50
|
-
}, [C, T]),
|
|
51
|
+
}, [C, T]), f(() => {
|
|
52
|
+
let e = [{
|
|
53
|
+
disabled: !k,
|
|
54
|
+
label: T`Download FASTA`,
|
|
55
|
+
onClick: N,
|
|
56
|
+
startIcon: /* @__PURE__ */ g(b, {})
|
|
57
|
+
}];
|
|
58
|
+
v(e);
|
|
59
|
+
let t = (t) => {
|
|
60
|
+
v([...t, ...e]);
|
|
61
|
+
}, r = n.getInstance();
|
|
62
|
+
return r.addEventListener("onFastaDownloadActionsChanged", t), r.emit("onFastaDownloadActionsRequested", {
|
|
63
|
+
caseIds: w.cases.map((e) => e.id),
|
|
64
|
+
caseTypeId: D.id,
|
|
65
|
+
geneticSequenceColId: k
|
|
66
|
+
}), () => {
|
|
67
|
+
r.removeEventListener("onFastaDownloadActionsChanged", t);
|
|
68
|
+
};
|
|
69
|
+
}, [
|
|
70
|
+
D.id,
|
|
71
|
+
k,
|
|
72
|
+
v,
|
|
73
|
+
N,
|
|
74
|
+
w.cases,
|
|
75
|
+
T
|
|
76
|
+
]), /* @__PURE__ */ g(l, { children: /* @__PURE__ */ g(l, {
|
|
51
77
|
sx: { marginBottom: 3 },
|
|
52
|
-
children: /* @__PURE__ */ g(
|
|
78
|
+
children: /* @__PURE__ */ g(i, {
|
|
53
79
|
...j,
|
|
54
80
|
children: /* @__PURE__ */ g("form", {
|
|
55
81
|
autoComplete: "off",
|
|
56
82
|
onSubmit: M,
|
|
57
|
-
children: /* @__PURE__ */ g(
|
|
83
|
+
children: /* @__PURE__ */ g(y, {
|
|
58
84
|
disabled: O.length < 2,
|
|
59
85
|
label: T`Genetic sequence column`,
|
|
60
86
|
name: "geneticSequenceColId",
|
|
@@ -63,20 +89,7 @@ var C = y(({ onClose: y, onTitleChange: C, openProps: w }) => {
|
|
|
63
89
|
})
|
|
64
90
|
})
|
|
65
91
|
})
|
|
66
|
-
})
|
|
67
|
-
sx: {
|
|
68
|
-
display: "flex",
|
|
69
|
-
gap: 1,
|
|
70
|
-
justifyContent: "flex-end",
|
|
71
|
-
marginBottom: 1
|
|
72
|
-
},
|
|
73
|
-
children: /* @__PURE__ */ g(c, { children: /* @__PURE__ */ g(l, {
|
|
74
|
-
color: "primary",
|
|
75
|
-
onClick: N,
|
|
76
|
-
startIcon: /* @__PURE__ */ g(x, {}),
|
|
77
|
-
children: T`Download FASTA`
|
|
78
|
-
}) })
|
|
79
|
-
})] });
|
|
92
|
+
}) });
|
|
80
93
|
}, {
|
|
81
94
|
defaultTitle: "",
|
|
82
95
|
fullWidth: !0,
|
|
@@ -84,6 +97,6 @@ var C = y(({ onClose: y, onTitleChange: C, openProps: w }) => {
|
|
|
84
97
|
testId: "SequenceDownloadDialog"
|
|
85
98
|
});
|
|
86
99
|
//#endregion
|
|
87
|
-
export {
|
|
100
|
+
export { S as SequenceDownloadDialog };
|
|
88
101
|
|
|
89
102
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/SequenceDownloadDialog/SequenceDownloadDialog.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\nimport {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/SequenceDownloadDialog/SequenceDownloadDialog.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\nimport { Box } from '@mui/material';\nimport DownloadIcon from '@mui/icons-material/Download';\nimport type { ReactElement } from 'react';\nimport {\n use,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport { useStore } from 'zustand';\nimport { useShallow } from 'zustand/shallow';\nimport {\n FormProvider,\n useForm,\n} from 'react-hook-form';\nimport type { CaseDbCase } from '@gen-epix/api-casedb';\nimport { CaseDbColType } from '@gen-epix/api-casedb';\nimport type {\n WithDialogRefMethods,\n WithDialogRenderProps,\n} from '@gen-epix/ui-core-components/hoc/withDialog';\nimport { AuthenticationService } from '@gen-epix/ui-client-common/classes/services/AuthenticationService';\nimport { ConfigService } from '@gen-epix/ui-client-common/classes/services/ConfigService';\nimport { withDialog } from '@gen-epix/ui-core-components/hoc/withDialog';\nimport { StringUtil } from '@gen-epix/ui-core/utils/StringUtil';\nimport { DownloadUtil } from '@gen-epix/ui-core/utils/DownloadUtil';\nimport { Autocomplete } from '@gen-epix/ui-core-form/components/fields/Autocomplete';\nimport type { AutoCompleteOption } from '@gen-epix/ui-core-form/models/form';\nimport type { DialogAction } from '@gen-epix/ui-core-components/components/Dialog';\n\nimport { DashboardStoreContext } from '../../../stores/dashboardStore';\nimport { EventBusService } from '../../../classes/services/EventBusService';\n\n\nexport interface SequenceDownloadDialogOpenProps {\n cases: CaseDbCase[];\n geneticSequenceColId?: string;\n}\n\nexport interface SequenceDownloadDialogProps extends WithDialogRenderProps<SequenceDownloadDialogOpenProps> {\n //\n}\n\nexport type SequenceDownloadDialogRefMethods = WithDialogRefMethods<SequenceDownloadDialogProps, SequenceDownloadDialogOpenProps>;\n\ntype FormFields = {\n geneticSequenceColId: string;\n};\n\nexport const SequenceDownloadDialog = withDialog<SequenceDownloadDialogProps, SequenceDownloadDialogOpenProps>((\n {\n onActionsChange,\n onClose,\n onTitleChange,\n openProps,\n }: SequenceDownloadDialogProps,\n): ReactElement => {\n const { t } = useTranslation();\n const dashboardStore = use(DashboardStoreContext);\n const completeCaseType = useStore(dashboardStore, useShallow((state) => state.completeCaseType));\n\n const geneticSequenceColOptions = useMemo<AutoCompleteOption<string>[]>(() => {\n const options: AutoCompleteOption<string>[] = [];\n\n completeCaseType.ordered_dim_ids.map(x => completeCaseType.dims[x]).forEach((dim) => {\n completeCaseType.ordered_col_ids_by_dim[dim.id].map(id => completeCaseType.cols[id]).forEach(col => {\n const refCol = completeCaseType.ref_cols[col.ref_col_id];\n if (refCol?.col_type === CaseDbColType.GENETIC_SEQUENCE) {\n options.push({\n label: col.label,\n value: col.id,\n });\n }\n });\n });\n return options;\n }, [completeCaseType]);\n\n const [geneticSequenceColId, setGeneticSequenceColId] = useState(openProps?.geneticSequenceColId ?? geneticSequenceColOptions?.length === 1 ? geneticSequenceColOptions[0].value : '');\n\n const formMethods = useForm<FormFields>({\n values: {\n geneticSequenceColId,\n },\n });\n\n const onSubmit = useCallback(() => {\n // noop, as the download is triggered by a button outside of the form\n }, []);\n\n const onDownloadFastaButtonClick = useCallback(() => {\n DownloadUtil.downloadAsMultiPartForm({\n accessToken: AuthenticationService.getInstance().authContextProps?.user?.access_token ?? '',\n action: `${ConfigService.getInstance().config.getAPIBaseUrl()}/v1/retrieve/genetic_sequence/fasta`,\n data: {\n case_ids: openProps.cases.map(c => c.id),\n case_type_id: completeCaseType.id,\n file_name: `${StringUtil.createSlug(completeCaseType.name)}-${StringUtil.createSlug(geneticSequenceColOptions.find(x => x.value === geneticSequenceColId)?.label)}-sequences.fasta`,\n genetic_sequence_col_id: geneticSequenceColId,\n },\n });\n onClose();\n }, [completeCaseType.id, completeCaseType.name, geneticSequenceColId, geneticSequenceColOptions, onClose, openProps.cases]);\n\n useEffect(() => {\n onTitleChange(t`Download sequences`);\n }, [onTitleChange, t]);\n\n useEffect(() => {\n const buildInItems: DialogAction[] = [\n {\n disabled: !geneticSequenceColId,\n label: t`Download FASTA`,\n onClick: onDownloadFastaButtonClick,\n startIcon: <DownloadIcon />,\n },\n ];\n onActionsChange(buildInItems);\n\n const onDownloadActionsChanged = (actions: DialogAction[]) => {\n onActionsChange([...actions, ...buildInItems]);\n };\n\n const eventBusService = EventBusService.getInstance();\n eventBusService.addEventListener('onFastaDownloadActionsChanged', onDownloadActionsChanged);\n eventBusService.emit('onFastaDownloadActionsRequested', {\n caseIds: openProps.cases.map(c => c.id),\n caseTypeId: completeCaseType.id,\n geneticSequenceColId,\n });\n return () => {\n eventBusService.removeEventListener('onFastaDownloadActionsChanged', onDownloadActionsChanged);\n };\n\n }, [completeCaseType.id, geneticSequenceColId, onActionsChange, onDownloadFastaButtonClick, openProps.cases, t]);\n\n return (\n <Box>\n <Box\n sx={{\n marginBottom: 3,\n }}\n >\n <FormProvider {...formMethods}>\n <form\n autoComplete={'off'}\n onSubmit={onSubmit}\n >\n <Autocomplete\n disabled={geneticSequenceColOptions.length < 2}\n label={t`Genetic sequence column`}\n name={'geneticSequenceColId'}\n // eslint-disable-next-line @eslint-react/kit/jsx-no-bind\n onChange={(value: string) => setGeneticSequenceColId(value)}\n options={geneticSequenceColOptions}\n />\n </form>\n </FormProvider>\n </Box>\n </Box>\n );\n}, {\n defaultTitle: '',\n fullWidth: true,\n maxWidth: 'lg',\n testId: 'SequenceDownloadDialog',\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmDA,IAAa,IAAyB,GACpC,EACE,oBACA,YACA,kBACA,mBAEe;CACjB,IAAM,EAAE,SAAM,EAAe,GACvB,IAAiB,EAAI,CAAqB,GAC1C,IAAmB,EAAS,GAAgB,GAAY,MAAU,EAAM,gBAAgB,CAAC,GAEzF,IAA4B,QAA4C;EAC5E,IAAM,IAAwC,CAAC;EAa/C,OAXA,EAAiB,gBAAgB,KAAI,MAAK,EAAiB,KAAK,EAAE,CAAC,CAAC,SAAS,MAAQ;GACnF,EAAiB,uBAAuB,EAAI,GAAG,CAAC,KAAI,MAAM,EAAiB,KAAK,EAAG,CAAC,CAAC,SAAQ,MAAO;IAElG,AADe,EAAiB,SAAS,EAAI,WACzC,EAAQ,aAAa,EAAc,oBACrC,EAAQ,KAAK;KACX,OAAO,EAAI;KACX,OAAO,EAAI;IACb,CAAC;GAEL,CAAC;EACH,CAAC,GACM;CACT,GAAG,CAAC,CAAgB,CAAC,GAEf,CAAC,GAAsB,KAA2B,EAAS,GAAW,wBAAwB,GAA2B,WAAW,IAAI,EAA0B,EAAE,CAAC,QAAQ,EAAE,GAE/K,IAAc,EAAoB,EACtC,QAAQ,EACN,wBACF,EACF,CAAC,GAEK,IAAW,QAAkB,CAEnC,GAAG,CAAC,CAAC,GAEC,IAA6B,QAAkB;EAWnD,AAVA,EAAa,wBAAwB;GACnC,aAAa,EAAsB,YAAY,CAAC,CAAC,kBAAkB,MAAM,gBAAgB;GACzF,QAAQ,GAAG,EAAc,YAAY,CAAC,CAAC,OAAO,cAAc,EAAE;GAC9D,MAAM;IACJ,UAAU,EAAU,MAAM,KAAI,MAAK,EAAE,EAAE;IACvC,cAAc,EAAiB;IAC/B,WAAW,GAAG,EAAW,WAAW,EAAiB,IAAI,EAAE,GAAG,EAAW,WAAW,EAA0B,MAAK,MAAK,EAAE,UAAU,CAAoB,CAAC,EAAE,KAAK,EAAE;IAClK,yBAAyB;GAC3B;EACF,CAAC,GACD,EAAQ;CACV,GAAG;EAAC,EAAiB;EAAI,EAAiB;EAAM;EAAsB;EAA2B;EAAS,EAAU;CAAK,CAAC;CAkC1H,OAhCA,QAAgB;EACd,EAAc,CAAC,oBAAoB;CACrC,GAAG,CAAC,GAAe,CAAC,CAAC,GAErB,QAAgB;EACd,IAAM,IAA+B,CACnC;GACE,UAAU,CAAC;GACX,OAAO,CAAC;GACR,SAAS;GACT,WAAW,kBAAC,GAAD,CAAe,CAAA;EAC5B,CACF;EACA,EAAgB,CAAY;EAE5B,IAAM,KAA4B,MAA4B;GAC5D,EAAgB,CAAC,GAAG,GAAS,GAAG,CAAY,CAAC;EAC/C,GAEM,IAAkB,EAAgB,YAAY;EAOpD,OANA,EAAgB,iBAAiB,iCAAiC,CAAwB,GAC1F,EAAgB,KAAK,mCAAmC;GACtD,SAAS,EAAU,MAAM,KAAI,MAAK,EAAE,EAAE;GACtC,YAAY,EAAiB;GAC7B;EACF,CAAC,SACY;GACX,EAAgB,oBAAoB,iCAAiC,CAAwB;EAC/F;CAEF,GAAG;EAAC,EAAiB;EAAI;EAAsB;EAAiB;EAA4B,EAAU;EAAO;CAAC,CAAC,GAG7G,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EACE,IAAI,EACF,cAAc,EAChB;EAEA,UAAA,kBAAC,GAAD;GAAc,GAAI;GAChB,UAAA,kBAAC,QAAD;IACE,cAAc;IACJ;IAEV,UAAA,kBAAC,GAAD;KACE,UAAU,EAA0B,SAAS;KAC7C,OAAO,CAAC;KACR,MAAM;KAEN,WAAW,MAAkB,EAAwB,CAAK;KAC1D,SAAS;IACV,CAAA;GACG,CAAA;EACM,CAAA;CACX,CAAA,EACF,CAAA;AAET,GAAG;CACD,cAAc;CACd,WAAW;CACX,UAAU;CACV,QAAQ;AACV,CAAC"}
|
|
@@ -310,7 +310,10 @@ var v = () => {
|
|
|
310
310
|
}, []);
|
|
311
311
|
let st = f((e) => {
|
|
312
312
|
C(e);
|
|
313
|
-
}, [C]), $ = m(() => new ye({
|
|
313
|
+
}, [C]), $ = m(() => new ye({
|
|
314
|
+
highlightedNodeNames: E.highlightSubject.data.caseIds,
|
|
315
|
+
persistedHighlightedNodeNames: []
|
|
316
|
+
}), [E.highlightSubject.data.caseIds]);
|
|
314
317
|
return p(() => {
|
|
315
318
|
let e = $.subscribe((e) => {
|
|
316
319
|
E.highlight({
|
|
@@ -323,7 +326,10 @@ var v = () => {
|
|
|
323
326
|
};
|
|
324
327
|
}, [$, E]), p(() => {
|
|
325
328
|
let e = E.highlightSubject.subscribe((e) => {
|
|
326
|
-
e.origin !== i.TREE && $.next({
|
|
329
|
+
e.origin !== i.TREE && $.next({
|
|
330
|
+
highlightedNodeNames: e.caseIds,
|
|
331
|
+
persistedHighlightedNodeNames: []
|
|
332
|
+
});
|
|
327
333
|
});
|
|
328
334
|
return () => {
|
|
329
335
|
e();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/TreeWidget/TreeWidget.tsx"],"sourcesContent":["import {\n Box,\n Button,\n ListItemIcon,\n ListItemText,\n MenuItem,\n useTheme,\n} from '@mui/material';\nimport ClearIcon from '@mui/icons-material/Clear';\nimport ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';\nimport FilterAltIcon from '@mui/icons-material/FilterAlt';\nimport InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';\nimport LinkIcon from '@mui/icons-material/Link';\nimport type { ReactElement } from 'react';\nimport {\n use,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useStore } from 'zustand';\nimport { useDebouncedCallback } from 'use-debounce';\nimport type { CaseDbRetrievePhylogeneticTreeRequestBody } from '@gen-epix/api-casedb';\nimport { CaseDbCaseApi } from '@gen-epix/api-casedb';\nimport type { MenuItemData } from '@gen-epix/ui-core-components/models/nestedMenu';\nimport { ConfigService } from '@gen-epix/ui-client-common/classes/services/ConfigService';\nimport { Spinner } from '@gen-epix/ui-core-components/components/Spinner';\nimport { useQueryMemo } from '@gen-epix/ui-client-common/hooks/useQueryMemo';\nimport { Subject } from '@gen-epix/ui-core/classes/Subject';\nimport { PhylogeneticTree } from '@gen-epix/ui-phylogenetic-tree/components/PhylogeneticTree';\nimport type {\n PhylogeneticTreeHighlightedNodeNamesSubjectValue,\n PhylogeneticTreePathClickEvent,\n PhylogeneticTreeRef,\n PhylogeneticTreeViewState,\n} from '@gen-epix/ui-phylogenetic-tree/components/PhylogeneticTree';\n\nimport { EventBusService } from '../../../classes/services/EventBusService';\nimport type {\n DashboardTreeSettings,\n TreeConfiguration,\n TreeWidgetData,\n TreeWidgetDataPersistable,\n} from '../../../models/dashboard';\nimport type { Highlighting } from '../../../models/caseDb';\nimport { DashboardStoreContext } from '../../../stores/dashboardStore';\nimport { SELECTION_FILTER_GROUP } from '../../../utils/CaseTypeUtil';\nimport { CaseDbDownloadUtil } from '../../../utils/CaseDbDownloadUtil';\nimport { TreeWidgetUtil } from '../../../utils/TreeWidgetUtil';\nimport type { ContextMenuConfigWithPosition } from '../ContextMenu';\nimport { ContextMenu } from '../ContextMenu';\nimport { TreeDescription } from '../TreeDescription';\nimport { WidgetUnavailable } from '../WidgetUnavailable';\nimport { CASEDB_QUERY_KEY } from '../../../constants/query';\nimport type { CaseDbConfig } from '../../../models/config';\nimport { TreeFilter } from '../../../classes/filters/TreeFilter';\nimport { DashboardWidget } from '../Dashboard';\nimport { DASHBOARD_COMPONENT_NAME } from '../../../constants/dashboard';\nimport { DashboardContext } from '../Dashboard/context/DashboardContext';\nimport { UserProfileStoreContext } from '../../../stores/userProfileStore/userProfileStoreContext';\n\ntype ZoomInMenuItemConfig = {\n caseIds?: string[];\n rootId?: string;\n};\n\nexport const TreeWidget = () => {\n const theme = useTheme();\n const { t } = useTranslation();\n const treeConfig = ConfigService.getInstance<CaseDbConfig>().config.tree;\n const [treeCanvas, setTreeCanvas] = useState<HTMLCanvasElement>();\n const [isTreeLinked, setIsTreeLinked] = useState(true);\n const treeRef = useRef<PhylogeneticTreeRef>(null);\n const dashboardStore = use(DashboardStoreContext);\n const dashboardContext = use(DashboardContext);\n const userProfileStore = use(UserProfileStoreContext);\n const setPhylogeneticTreeResponse = useStore(dashboardStore, (state) => state.setPhylogeneticTreeResponse);\n const baseData = useStore(dashboardStore, (state) => state.baseData);\n const sortedData = useStore(dashboardStore, (state) => state.sortedData);\n const filteredCases = useStore(dashboardStore, (state) => state.filteredData[SELECTION_FILTER_GROUP]);\n const setSorting = useStore(dashboardStore, (state) => state.setSorting);\n const tree = useStore(dashboardStore, (state) => state.tree);\n const sortByField = useStore(dashboardStore, (state) => state.sortByField);\n const stratification = useStore(dashboardStore, (state) => state.stratification);\n const completeCaseType = useStore(dashboardStore, (state) => state.completeCaseType);\n const hasActiveTreeFilter = useStore(dashboardStore, (state) => !state.filters.find(filter => filter instanceof TreeFilter).isInitialFilterValue());\n const addTreeFilter = useStore(dashboardStore, (state) => state.addTreeFilter);\n const treeFilterStepOut = useStore(dashboardStore, (state) => state.treeFilterStepOut);\n const updateWidgetData = useStore(dashboardStore, (state) => state.updateWidgetData);\n const updateWidgetDataPersistable = useStore(dashboardStore, (state) => state.updateWidgetDataPersistable);\n const removeTreeFilter = useStore(dashboardStore, (state) => state.removeTreeFilter);\n const isCaseDataLoading = useStore(dashboardStore, (state) => state.isDataLoading);\n const newick = useStore(dashboardStore, (state) => state.newick);\n const resetTreeAddresses = useStore(dashboardStore, (state) => state.resetTreeAddresses);\n const isShowDistancesEnabled = useStore(userProfileStore, (state) => (state.dashboardWidgetSettings[DASHBOARD_COMPONENT_NAME.TREE] as DashboardTreeSettings).isShowDistancesEnabled);\n const isShowSupportLinesWhenUnlinkedEnabled = useStore(userProfileStore, (state) => (state.dashboardWidgetSettings[DASHBOARD_COMPONENT_NAME.TREE] as DashboardTreeSettings).isShowSupportLinesWhenUnlinkedEnabled);\n const [contextMenuConfig, setContextMenuConfig] = useState<ContextMenuConfigWithPosition | null>(null);\n const [zoomInMenuItemConfig, setZoomInMenuItemConfig] = useState<ZoomInMenuItemConfig>(null);\n const [extraLeafInfoId, setExtraLeafInfoId] = useState<string>(null);\n const [treeConfiguration, setTreeConfiguration] = useState<TreeConfiguration>(dashboardStore.getState().getWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE).treeConfiguration);\n const treeHighlightingSubject = useMemo(() => new Subject<Highlighting>({\n caseIds: [],\n origin: null,\n }), []);\n const initialTreeViewState = useMemo<PhylogeneticTreeViewState>(() => {\n const widgetData = dashboardStore.getState().getWidgetData<TreeWidgetData>(DASHBOARD_COMPONENT_NAME.TREE);\n return {\n horizontalScrollPosition: !isNaN(widgetData?.horizontalScrollPosition) ? widgetData.horizontalScrollPosition : 0,\n verticalScrollPosition: !isNaN(widgetData?.verticalScrollPosition) ? widgetData.verticalScrollPosition : 0,\n zoomLevel: !isNaN(widgetData?.zoomLevel) ? widgetData.zoomLevel : 1,\n };\n }, [dashboardStore]);\n\n const sortedLeafNames = useMemo(() => {\n return sortedData.map(c => c.id);\n }, [sortedData]);\n\n const treeConfigurations = useMemo(() => TreeWidgetUtil.getTreeConfigurations(completeCaseType), [completeCaseType]);\n\n const treeCanvasAriaLabel = useMemo(() => {\n if (!treeConfiguration) {\n return t('Phylogenetic tree visualization');\n }\n\n const label = treeConfiguration.col?.label || t('data');\n const geneticDistanceProtocol = treeConfiguration.geneticDistanceProtocol?.name || t('unknown protocol');\n const treeAlgorithm = treeConfiguration.treeAlgorithm?.name || t('unknown algorithm');\n\n return t('Figure of a phylogenetic tree belonging to {{label}}. Generated using {{geneticDistanceProtocol}} and {{treeAlgorithm}}.', {\n geneticDistanceProtocol,\n label,\n treeAlgorithm,\n });\n }, [treeConfiguration, t]);\n\n useEffect(() => {\n const unsubscribeHighlightingManager = dashboardContext.highlightSubject.subscribe((highlighting) => {\n if (highlighting.origin === DASHBOARD_COMPONENT_NAME.TREE) {\n return;\n }\n treeHighlightingSubject.next(highlighting);\n });\n const unsubscribeTreeHighlightingSubject = treeHighlightingSubject.subscribe((highlighting) => {\n if (highlighting.origin !== DASHBOARD_COMPONENT_NAME.TREE) {\n return;\n }\n dashboardContext.highlight(highlighting);\n });\n\n return () => {\n unsubscribeHighlightingManager();\n unsubscribeTreeHighlightingSubject();\n };\n }, [dashboardContext, treeHighlightingSubject]);\n\n const caseIds = useMemo(() => filteredCases.map(c => c.id).sort(), [filteredCases]);\n\n useEffect(() => {\n resetTreeAddresses();\n }, [caseIds, resetTreeAddresses]);\n\n const hasEnoughSequencesToShowTree = useMemo(() => caseIds.length >= 2 && caseIds.every(x => !!x), [caseIds]);\n const hasToManyResultsToShowTree = useMemo(() => caseIds.length > 0 && completeCaseType.props.read_max_tree_size > 0 && caseIds.length > completeCaseType.props.read_max_tree_size, [caseIds, completeCaseType.props.read_max_tree_size]);\n\n const retrievePhylogeneticTreeRequestBody = useMemo<CaseDbRetrievePhylogeneticTreeRequestBody>(() => ({\n case_ids: caseIds,\n case_type_id: completeCaseType.id,\n genetic_distance_col_id: treeConfiguration?.col.id,\n tree_algorithm_code: treeConfiguration?.treeAlgorithm.code,\n }), [caseIds, completeCaseType.id, treeConfiguration]);\n\n const { data: treeData, error: treeError, isLoading: isTreeLoading } = useQueryMemo({\n enabled: hasEnoughSequencesToShowTree && !!treeConfiguration && !hasToManyResultsToShowTree,\n queryFn: async ({ signal }) => {\n const response = await CaseDbCaseApi.getInstance().retrievePhylogeneticTree(retrievePhylogeneticTreeRequestBody, { signal });\n return response.data;\n },\n queryKey: [CASEDB_QUERY_KEY.PHYLOGENETIC_TREE, JSON.stringify(retrievePhylogeneticTreeRequestBody)],\n retry: false,\n staleTime: Infinity,\n });\n\n const onOpenFiltersButtonClick = useCallback(() => {\n EventBusService.getInstance().emit('openFiltersMenu');\n }, []);\n\n const isLoading = !!treeConfiguration && (isCaseDataLoading || (hasEnoughSequencesToShowTree && isTreeLoading));\n const isTreeUnavailable = hasToManyResultsToShowTree || !isCaseDataLoading && ((!isLoading && !!treeError) || !hasEnoughSequencesToShowTree || tree?.maxBranchLength?.toNumber() === 0 || tree?.size === 0 || !treeConfiguration || (!isLoading && tree === null));\n const shouldShowTree = !hasToManyResultsToShowTree && !!treeConfiguration && !isCaseDataLoading && !treeError && !isTreeLoading && tree?.size > 0 && hasEnoughSequencesToShowTree;\n\n const widgetUnavailableReason = useMemo<ReactElement | string>(() => {\n if (!isTreeUnavailable) {\n return null;\n }\n if (!hasEnoughSequencesToShowTree) {\n return t`The phylogenetic tree cannot be displayed because there are not enough sequences available for the shown cases. A minimum of 2 sequences is required to generate a phylogenetic tree.`;\n }\n if (hasEnoughSequencesToShowTree && !tree) {\n return t`The phylogenetic tree cannot be displayed because the tree could not be generated. Either there are no sequences available for the shown cases or the selected tree configuration does not yield a valid tree.`;\n }\n if (hasToManyResultsToShowTree) {\n return (\n (\n <>\n <Box\n sx={{\n marginBottom: 2,\n }}\n >\n {t('The phylogenetic tree cannot be displayed because the number of cases ({{caseCount}}) exceeds the maximum allowed number of cases ({{maxSize}}) to display a phylogenetic tree. Refine your filters to reduce the number of results.', {\n caseCount: caseIds.length,\n maxSize: completeCaseType.props.read_max_tree_size,\n })}\n </Box>\n <Button\n color={'inherit'}\n onClick={onOpenFiltersButtonClick}\n variant={'outlined'}\n >\n {t`Refine filters`}\n </Button>\n </>\n )\n );\n }\n }, [caseIds.length, completeCaseType.props.read_max_tree_size, hasEnoughSequencesToShowTree, hasToManyResultsToShowTree, isTreeUnavailable, onOpenFiltersButtonClick, t, tree]);\n\n useEffect(() => {\n if (treeData) {\n setPhylogeneticTreeResponse(treeData);\n }\n }, [treeData, setPhylogeneticTreeResponse]);\n\n useEffect(() => {\n if (!treeConfigurations?.length) {\n setTreeConfiguration(null);\n updateWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE, {\n treeConfiguration: null,\n });\n return;\n }\n const newConfig = dashboardStore.getState().getWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE)?.treeConfiguration || treeConfigurations[0];\n\n setTreeConfiguration(newConfig);\n updateWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE, {\n treeConfiguration: newConfig,\n });\n }, [dashboardStore, treeConfigurations, updateWidgetDataPersistable]);\n\n useEffect(() => {\n if (sortByField && isTreeLinked && shouldShowTree) {\n treeRef.current?.unlink({\n positionX: 0,\n positionY: 0,\n zoomLevel: ConfigService.getInstance<CaseDbConfig>().config.tree.INITIAL_UNLINKED_ZOOM_LEVEL,\n });\n }\n }, [isTreeLinked, shouldShowTree, sortByField]);\n\n const updateEpiTreeWidgetDataDebounced = useDebouncedCallback((viewState: PhylogeneticTreeViewState) => {\n updateWidgetData<TreeWidgetData>(DASHBOARD_COMPONENT_NAME.TREE, {\n horizontalScrollPosition: viewState.horizontalScrollPosition,\n verticalScrollPosition: viewState.verticalScrollPosition,\n zoomLevel: viewState.zoomLevel,\n });\n }, 500);\n\n const onTreeViewStateChange = useCallback((viewState: PhylogeneticTreeViewState) => {\n updateEpiTreeWidgetDataDebounced(viewState);\n }, [updateEpiTreeWidgetDataDebounced]);\n\n const onTreeCanvasChange = useCallback((canvas?: HTMLCanvasElement) => {\n setTreeCanvas(canvas);\n }, []);\n\n const onEpiContextMenuClose = useCallback(() => {\n setContextMenuConfig(null);\n setZoomInMenuItemConfig(null);\n }, []);\n\n const linkLineListToTree = useCallback(() => {\n const perform = async () => {\n await setSorting(null, null);\n treeRef.current?.syncScrollToVisibleTree();\n };\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n perform();\n\n }, [setSorting]);\n\n const onLinkButtonClick = useCallback(() => {\n linkLineListToTree();\n\n }, [linkLineListToTree]);\n\n const resetZoomLevelAndScrollPosition = useCallback(() => {\n treeRef.current?.link(0);\n }, []);\n\n const onAddTreeFilterMenuItemClick = useCallback(async (onMenuClose: () => void) => {\n await addTreeFilter(zoomInMenuItemConfig.rootId);\n resetZoomLevelAndScrollPosition();\n onMenuClose();\n }, [addTreeFilter, zoomInMenuItemConfig, resetZoomLevelAndScrollPosition]);\n\n const onRemoveTreeFilterButtonClick = useCallback(async () => {\n await removeTreeFilter();\n resetZoomLevelAndScrollPosition();\n }, [removeTreeFilter, resetZoomLevelAndScrollPosition]);\n\n const onTreeFilterStepOutButtonClick = useCallback(async () => {\n await treeFilterStepOut();\n resetZoomLevelAndScrollPosition();\n }, [resetZoomLevelAndScrollPosition, treeFilterStepOut]);\n\n const onTreePathClick = useCallback(({ mouseEvent, pathProperties }: PhylogeneticTreePathClickEvent) => {\n setZoomInMenuItemConfig(null);\n setExtraLeafInfoId(null);\n\n if (pathProperties?.treeNode) {\n const { treeNode } = pathProperties;\n setContextMenuConfig({\n caseIds: treeNode.subTreeLeaveNames ? treeNode.subTreeLeaveNames : [treeNode.name],\n mouseEvent,\n position: {\n left: mouseEvent.clientX,\n top: mouseEvent.clientY,\n },\n });\n if (treeNode.subTreeNames.length && treeNode.name && treeNode.maxBranchLength.toNumber()) {\n setZoomInMenuItemConfig({\n caseIds: treeNode.subTreeLeaveNames,\n rootId: treeNode.name,\n });\n }\n if (treeNode.name && !treeNode.subTreeNames.length) {\n setExtraLeafInfoId(treeNode.name);\n }\n return;\n }\n\n if (pathProperties?.subTreeLeaveNames?.length) {\n setContextMenuConfig({\n caseIds: pathProperties.subTreeLeaveNames,\n mouseEvent,\n position: {\n left: mouseEvent.clientX,\n top: mouseEvent.clientY,\n },\n });\n }\n }, []);\n\n const onShowDetailsSelectionMenuItemClick = useCallback((onMenuClose: () => void) => {\n EventBusService.getInstance().emit('openCaseInfoDialog', {\n caseId: baseData.find(c => c.id === extraLeafInfoId).id,\n caseTypeId: completeCaseType.id,\n });\n onMenuClose();\n }, [extraLeafInfoId, baseData, completeCaseType.id]);\n\n const getEpiContextMenuExtraItems = useCallback((onMenuClose: () => void): ReactElement => {\n if (zoomInMenuItemConfig) {\n return (\n <MenuItem\n divider\n // eslint-disable-next-line @eslint-react/kit/jsx-no-bind\n onClick={async () => onAddTreeFilterMenuItemClick(onMenuClose)}\n >\n <ListItemIcon>\n <FilterAltIcon fontSize={'small'} />\n </ListItemIcon>\n <ListItemText>\n {t`Filter (show only this subtree)`}\n </ListItemText>\n </MenuItem>\n );\n }\n if (extraLeafInfoId) {\n return (\n <MenuItem\n divider\n // eslint-disable-next-line @eslint-react/kit/jsx-no-bind\n onClick={() => onShowDetailsSelectionMenuItemClick(onMenuClose)}\n >\n <ListItemIcon>\n <InfoOutlinedIcon fontSize={'small'} />\n </ListItemIcon>\n <ListItemText>\n {t`Show details`}\n </ListItemText>\n </MenuItem>\n );\n }\n }, [extraLeafInfoId, onShowDetailsSelectionMenuItemClick, onAddTreeFilterMenuItemClick, t, zoomInMenuItemConfig]);\n\n const titleMenu = useMemo<MenuItemData | string>(() => {\n if (!treeConfigurations?.length) {\n return t`Tree`;\n }\n\n const menu: MenuItemData = {\n disabled: !treeConfiguration,\n items: treeConfigurations?.map<MenuItemData>(config => ({\n active: !!treeConfiguration && treeConfiguration.computedId === config.computedId,\n callback: () => {\n const perform = async () => {\n await removeTreeFilter();\n updateWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE, {\n treeConfiguration: config,\n });\n setTreeConfiguration(config);\n };\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n perform();\n },\n label: TreeWidgetUtil.getTreeConfigurationLabel(config),\n tooltip: (\n <TreeDescription\n treeConfiguration={config}\n />\n ),\n })) ?? [],\n label: treeConfiguration ? t('Tree: {{algorithm}}', { algorithm: TreeWidgetUtil.getTreeConfigurationLabel(treeConfiguration) }) : t`Tree`,\n tooltip: treeConfiguration\n ? (\n <TreeDescription\n treeConfiguration={treeConfiguration}\n />\n )\n : undefined,\n };\n\n return menu;\n }, [treeConfigurations, treeConfiguration, t, removeTreeFilter, updateWidgetDataPersistable]);\n\n const primaryMenu = useMemo<MenuItemData[]>(() => {\n return [\n {\n callback: onTreeFilterStepOutButtonClick,\n disabled: !hasActiveTreeFilter,\n label: t`Change tree filter to nearest ancestor`,\n leftIcon: <ArrowUpwardIcon />,\n },\n {\n callback: onRemoveTreeFilterButtonClick,\n disabled: !hasActiveTreeFilter,\n label: t`Remove tree filter`,\n leftIcon: <ClearIcon />,\n },\n {\n callback: onLinkButtonClick,\n label: t`Link and snap the Line List to the Tree (resets tree zoom level and Line List sorting)`,\n leftIcon: (\n <LinkIcon\n sx={{\n color: isTreeLinked ? undefined : theme.palette.error.main,\n }}\n />\n ),\n },\n ];\n }, [hasActiveTreeFilter, isTreeLinked, onLinkButtonClick, onRemoveTreeFilterButtonClick, onTreeFilterStepOutButtonClick, t, theme.palette.error.main]);\n\n useEffect(() => {\n const emitDownloadOptions = () => {\n const baseName = t('Phylogenetic Tree - {{geneticDistanceProtocol}} - {{treeAlgorithm}}',\n {\n geneticDistanceProtocol: treeConfiguration?.geneticDistanceProtocol.name ?? '',\n treeAlgorithm: treeConfiguration?.treeAlgorithm.name ?? '',\n });\n\n EventBusService.getInstance().emit('onDownloadOptionsChanged', {\n disabled: isTreeUnavailable,\n items: [\n {\n callback: () => CaseDbDownloadUtil.downloadNewick(baseName, newick, completeCaseType, t),\n label: t`Save as Newick`,\n },\n {\n callback: () => CaseDbDownloadUtil.downloadCanvasImage(baseName, treeCanvas, 'jpeg', completeCaseType, t),\n label: t`Save as JPEG`,\n },\n {\n callback: () => CaseDbDownloadUtil.downloadCanvasImage(baseName, treeCanvas, 'png', completeCaseType, t),\n label: t`Save as PNG`,\n },\n ],\n zone: DASHBOARD_COMPONENT_NAME.TREE,\n zoneLabel: t`Phylogenetic Tree`,\n });\n };\n\n\n emitDownloadOptions();\n const eventBusService = EventBusService.getInstance();\n eventBusService.addEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n\n return () => {\n eventBusService.emit('onDownloadOptionsChanged', {\n items: null,\n zone: DASHBOARD_COMPONENT_NAME.TREE,\n zoneLabel: t`Tree`,\n });\n eventBusService.removeEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n };\n }, [completeCaseType, isTreeLinked, isTreeUnavailable, newick, t, treeCanvas, treeConfiguration?.geneticDistanceProtocol.name, treeConfiguration?.treeAlgorithm.name]);\n\n\n useEffect(() => {\n // eslint-disable-next-line @eslint-react/web-api-no-leaked-event-listener\n const removeEventListener = EventBusService.getInstance().addEventListener('onLinkLineListAndTree', () => treeRef.current?.link());\n return () => {\n removeEventListener();\n };\n }, []);\n\n\n const onLinkStateChange = useCallback((linked: boolean) => {\n setIsTreeLinked(linked);\n }, [setIsTreeLinked]);\n\n const phylogeneticTreeHighlightedNodeNamesSubject = useMemo(() => {\n const subject = new Subject<PhylogeneticTreeHighlightedNodeNamesSubjectValue>({\n highlightedNodeNames: dashboardContext.highlightSubject.data.caseIds,\n });\n return subject;\n }, [dashboardContext.highlightSubject.data.caseIds]);\n\n useEffect(() => {\n const unsubscribe = phylogeneticTreeHighlightedNodeNamesSubject.subscribe((data) => {\n dashboardContext.highlight({\n caseIds: data.highlightedNodeNames,\n origin: DASHBOARD_COMPONENT_NAME.TREE,\n });\n });\n return () => {\n unsubscribe();\n };\n }, [phylogeneticTreeHighlightedNodeNamesSubject, dashboardContext]);\n\n useEffect(() => {\n const unsubscribe = dashboardContext.highlightSubject.subscribe(data => {\n if (data.origin === DASHBOARD_COMPONENT_NAME.TREE) {\n return;\n }\n phylogeneticTreeHighlightedNodeNamesSubject.next({\n highlightedNodeNames: data.caseIds,\n });\n });\n return () => {\n unsubscribe();\n };\n }, [dashboardContext.highlightSubject, phylogeneticTreeHighlightedNodeNamesSubject]);\n\n return (\n <DashboardWidget\n expandDisabled={isTreeUnavailable}\n primaryMenu={primaryMenu}\n title={titleMenu}\n widgetName={DASHBOARD_COMPONENT_NAME.TREE}\n >\n <Box\n sx={{\n height: '100%',\n overflow: 'clip',\n position: 'relative',\n width: '100%',\n }}\n >\n {isTreeUnavailable && (\n <>\n <WidgetUnavailable\n reason={widgetUnavailableReason}\n widgetLabel={t`phylogenetic tree`}\n />\n </>\n )}\n {(isLoading && !isTreeUnavailable) && (\n <Spinner\n label={t`Loading`}\n takingLongerTimeoutMs={ConfigService.getInstance<CaseDbConfig>().config.tree.TAKING_LONGER_TIMEOUT_MS}\n />\n )}\n {!isTreeUnavailable && shouldShowTree && (\n <PhylogeneticTree\n ancestorDotRadius={treeConfig.ANCESTOR_DOT_RADIUS}\n ariaLabel={treeCanvasAriaLabel}\n backgroundColor={theme.palette.background.paper}\n dimFn={theme['gen-epix-ui-client-casedb'].tree.dimFn}\n fontFamily={theme.typography.fontFamily}\n headerHeight={treeConfig.HEADER_HEIGHT}\n highlightedNodeNamesSubject={phylogeneticTreeHighlightedNodeNamesSubject}\n initialViewState={initialTreeViewState}\n itemHeight={ConfigService.getInstance<CaseDbConfig>().config.lineList.TABLE_ROW_HEIGHT}\n leafDotRadius={treeConfig.LEAF_DOT_RADIUS}\n linkedScrollDebounceDelayMs={treeConfig.LINKED_SCROLL_DEBOUNCE_DELAY_MS}\n maxScaleWidthPx={treeConfig.MAX_SCALE_WIDTH_PX}\n maxZoomLevel={treeConfig.MAX_ZOOM_LEVEL}\n maxZoomSpeed={treeConfig.MAX_ZOOM_SPEED}\n minimumDistancePercentageToShowLabel={treeConfig.MINIMUM_DISTANCE_PERCENTAGE_TO_SHOW_LABEL}\n minScaleWidthPx={treeConfig.MIN_SCALE_WIDTH_PX}\n minZoomLevel={treeConfig.MIN_ZOOM_LEVEL}\n minZoomSpeed={treeConfig.MIN_ZOOM_SPEED}\n nodeNameColors={stratification?.caseIdColors}\n onCanvasChange={onTreeCanvasChange}\n onLinkStateChange={onLinkStateChange}\n onPathClick={onTreePathClick}\n onViewStateChange={onTreeViewStateChange}\n panningThreshold={treeConfig.PANNING_THRESHOLD}\n ref={treeRef}\n regularFillColorSupportLine={treeConfig.REGULAR_FILL_COLOR_SUPPORT_LINE}\n scaleColor={theme.palette.text.primary}\n scaleIncrements={treeConfig.SCALE_INCREMENTS}\n scrollSubject={dashboardContext.linkedScrollSubject}\n shouldShowDistances={isShowDistancesEnabled}\n shouldShowSupportLinesWhenUnlinked={isShowSupportLinesWhenUnlinkedEnabled}\n sortedLeafNames={sortedLeafNames}\n supportLineColorLinked={theme['gen-epix-ui-client-casedb'].tree.supportLineColorLinked}\n supportLineColorUnlinked={theme['gen-epix-ui-client-casedb'].tree.supportLineColorUnlinked}\n tree={tree}\n treeColor={theme['gen-epix-ui-client-casedb'].tree.color}\n treeFont={theme['gen-epix-ui-client-casedb'].tree.font}\n treePadding={treeConfig.TREE_PADDING}\n visibleRangeSubject={dashboardContext.lineListRangeSubject}\n />\n )}\n </Box>\n <ContextMenu\n config={contextMenuConfig}\n getExtraItems={getEpiContextMenuExtraItems}\n onMenuClose={onEpiContextMenuClose}\n />\n </DashboardWidget>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/ui/TreeWidget/TreeWidget.tsx"],"sourcesContent":["import {\n Box,\n Button,\n ListItemIcon,\n ListItemText,\n MenuItem,\n useTheme,\n} from '@mui/material';\nimport ClearIcon from '@mui/icons-material/Clear';\nimport ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';\nimport FilterAltIcon from '@mui/icons-material/FilterAlt';\nimport InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';\nimport LinkIcon from '@mui/icons-material/Link';\nimport type { ReactElement } from 'react';\nimport {\n use,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { useStore } from 'zustand';\nimport { useDebouncedCallback } from 'use-debounce';\nimport type { CaseDbRetrievePhylogeneticTreeRequestBody } from '@gen-epix/api-casedb';\nimport { CaseDbCaseApi } from '@gen-epix/api-casedb';\nimport type { MenuItemData } from '@gen-epix/ui-core-components/models/nestedMenu';\nimport { ConfigService } from '@gen-epix/ui-client-common/classes/services/ConfigService';\nimport { Spinner } from '@gen-epix/ui-core-components/components/Spinner';\nimport { useQueryMemo } from '@gen-epix/ui-client-common/hooks/useQueryMemo';\nimport { Subject } from '@gen-epix/ui-core/classes/Subject';\nimport { PhylogeneticTree } from '@gen-epix/ui-phylogenetic-tree/components/PhylogeneticTree';\nimport type {\n PhylogeneticTreeHighlightedNodeNamesSubjectValue,\n PhylogeneticTreePathClickEvent,\n PhylogeneticTreeRef,\n PhylogeneticTreeViewState,\n} from '@gen-epix/ui-phylogenetic-tree/components/PhylogeneticTree';\n\nimport { EventBusService } from '../../../classes/services/EventBusService';\nimport type {\n DashboardTreeSettings,\n TreeConfiguration,\n TreeWidgetData,\n TreeWidgetDataPersistable,\n} from '../../../models/dashboard';\nimport type { Highlighting } from '../../../models/caseDb';\nimport { DashboardStoreContext } from '../../../stores/dashboardStore';\nimport { SELECTION_FILTER_GROUP } from '../../../utils/CaseTypeUtil';\nimport { CaseDbDownloadUtil } from '../../../utils/CaseDbDownloadUtil';\nimport { TreeWidgetUtil } from '../../../utils/TreeWidgetUtil';\nimport type { ContextMenuConfigWithPosition } from '../ContextMenu';\nimport { ContextMenu } from '../ContextMenu';\nimport { TreeDescription } from '../TreeDescription';\nimport { WidgetUnavailable } from '../WidgetUnavailable';\nimport { CASEDB_QUERY_KEY } from '../../../constants/query';\nimport type { CaseDbConfig } from '../../../models/config';\nimport { TreeFilter } from '../../../classes/filters/TreeFilter';\nimport { DashboardWidget } from '../Dashboard';\nimport { DASHBOARD_COMPONENT_NAME } from '../../../constants/dashboard';\nimport { DashboardContext } from '../Dashboard/context/DashboardContext';\nimport { UserProfileStoreContext } from '../../../stores/userProfileStore/userProfileStoreContext';\n\ntype ZoomInMenuItemConfig = {\n caseIds?: string[];\n rootId?: string;\n};\n\nexport const TreeWidget = () => {\n const theme = useTheme();\n const { t } = useTranslation();\n const treeConfig = ConfigService.getInstance<CaseDbConfig>().config.tree;\n const [treeCanvas, setTreeCanvas] = useState<HTMLCanvasElement>();\n const [isTreeLinked, setIsTreeLinked] = useState(true);\n const treeRef = useRef<PhylogeneticTreeRef>(null);\n const dashboardStore = use(DashboardStoreContext);\n const dashboardContext = use(DashboardContext);\n const userProfileStore = use(UserProfileStoreContext);\n const setPhylogeneticTreeResponse = useStore(dashboardStore, (state) => state.setPhylogeneticTreeResponse);\n const baseData = useStore(dashboardStore, (state) => state.baseData);\n const sortedData = useStore(dashboardStore, (state) => state.sortedData);\n const filteredCases = useStore(dashboardStore, (state) => state.filteredData[SELECTION_FILTER_GROUP]);\n const setSorting = useStore(dashboardStore, (state) => state.setSorting);\n const tree = useStore(dashboardStore, (state) => state.tree);\n const sortByField = useStore(dashboardStore, (state) => state.sortByField);\n const stratification = useStore(dashboardStore, (state) => state.stratification);\n const completeCaseType = useStore(dashboardStore, (state) => state.completeCaseType);\n const hasActiveTreeFilter = useStore(dashboardStore, (state) => !state.filters.find(filter => filter instanceof TreeFilter).isInitialFilterValue());\n const addTreeFilter = useStore(dashboardStore, (state) => state.addTreeFilter);\n const treeFilterStepOut = useStore(dashboardStore, (state) => state.treeFilterStepOut);\n const updateWidgetData = useStore(dashboardStore, (state) => state.updateWidgetData);\n const updateWidgetDataPersistable = useStore(dashboardStore, (state) => state.updateWidgetDataPersistable);\n const removeTreeFilter = useStore(dashboardStore, (state) => state.removeTreeFilter);\n const isCaseDataLoading = useStore(dashboardStore, (state) => state.isDataLoading);\n const newick = useStore(dashboardStore, (state) => state.newick);\n const resetTreeAddresses = useStore(dashboardStore, (state) => state.resetTreeAddresses);\n const isShowDistancesEnabled = useStore(userProfileStore, (state) => (state.dashboardWidgetSettings[DASHBOARD_COMPONENT_NAME.TREE] as DashboardTreeSettings).isShowDistancesEnabled);\n const isShowSupportLinesWhenUnlinkedEnabled = useStore(userProfileStore, (state) => (state.dashboardWidgetSettings[DASHBOARD_COMPONENT_NAME.TREE] as DashboardTreeSettings).isShowSupportLinesWhenUnlinkedEnabled);\n const [contextMenuConfig, setContextMenuConfig] = useState<ContextMenuConfigWithPosition | null>(null);\n const [zoomInMenuItemConfig, setZoomInMenuItemConfig] = useState<ZoomInMenuItemConfig>(null);\n const [extraLeafInfoId, setExtraLeafInfoId] = useState<string>(null);\n const [treeConfiguration, setTreeConfiguration] = useState<TreeConfiguration>(dashboardStore.getState().getWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE).treeConfiguration);\n const treeHighlightingSubject = useMemo(() => new Subject<Highlighting>({\n caseIds: [],\n origin: null,\n }), []);\n const initialTreeViewState = useMemo<PhylogeneticTreeViewState>(() => {\n const widgetData = dashboardStore.getState().getWidgetData<TreeWidgetData>(DASHBOARD_COMPONENT_NAME.TREE);\n return {\n horizontalScrollPosition: !isNaN(widgetData?.horizontalScrollPosition) ? widgetData.horizontalScrollPosition : 0,\n verticalScrollPosition: !isNaN(widgetData?.verticalScrollPosition) ? widgetData.verticalScrollPosition : 0,\n zoomLevel: !isNaN(widgetData?.zoomLevel) ? widgetData.zoomLevel : 1,\n };\n }, [dashboardStore]);\n\n const sortedLeafNames = useMemo(() => {\n return sortedData.map(c => c.id);\n }, [sortedData]);\n\n const treeConfigurations = useMemo(() => TreeWidgetUtil.getTreeConfigurations(completeCaseType), [completeCaseType]);\n\n const treeCanvasAriaLabel = useMemo(() => {\n if (!treeConfiguration) {\n return t('Phylogenetic tree visualization');\n }\n\n const label = treeConfiguration.col?.label || t('data');\n const geneticDistanceProtocol = treeConfiguration.geneticDistanceProtocol?.name || t('unknown protocol');\n const treeAlgorithm = treeConfiguration.treeAlgorithm?.name || t('unknown algorithm');\n\n return t('Figure of a phylogenetic tree belonging to {{label}}. Generated using {{geneticDistanceProtocol}} and {{treeAlgorithm}}.', {\n geneticDistanceProtocol,\n label,\n treeAlgorithm,\n });\n }, [treeConfiguration, t]);\n\n useEffect(() => {\n const unsubscribeHighlightingManager = dashboardContext.highlightSubject.subscribe((highlighting) => {\n if (highlighting.origin === DASHBOARD_COMPONENT_NAME.TREE) {\n return;\n }\n treeHighlightingSubject.next(highlighting);\n });\n const unsubscribeTreeHighlightingSubject = treeHighlightingSubject.subscribe((highlighting) => {\n if (highlighting.origin !== DASHBOARD_COMPONENT_NAME.TREE) {\n return;\n }\n dashboardContext.highlight(highlighting);\n });\n\n return () => {\n unsubscribeHighlightingManager();\n unsubscribeTreeHighlightingSubject();\n };\n }, [dashboardContext, treeHighlightingSubject]);\n\n const caseIds = useMemo(() => filteredCases.map(c => c.id).sort(), [filteredCases]);\n\n useEffect(() => {\n resetTreeAddresses();\n }, [caseIds, resetTreeAddresses]);\n\n const hasEnoughSequencesToShowTree = useMemo(() => caseIds.length >= 2 && caseIds.every(x => !!x), [caseIds]);\n const hasToManyResultsToShowTree = useMemo(() => caseIds.length > 0 && completeCaseType.props.read_max_tree_size > 0 && caseIds.length > completeCaseType.props.read_max_tree_size, [caseIds, completeCaseType.props.read_max_tree_size]);\n\n const retrievePhylogeneticTreeRequestBody = useMemo<CaseDbRetrievePhylogeneticTreeRequestBody>(() => ({\n case_ids: caseIds,\n case_type_id: completeCaseType.id,\n genetic_distance_col_id: treeConfiguration?.col.id,\n tree_algorithm_code: treeConfiguration?.treeAlgorithm.code,\n }), [caseIds, completeCaseType.id, treeConfiguration]);\n\n const { data: treeData, error: treeError, isLoading: isTreeLoading } = useQueryMemo({\n enabled: hasEnoughSequencesToShowTree && !!treeConfiguration && !hasToManyResultsToShowTree,\n queryFn: async ({ signal }) => {\n const response = await CaseDbCaseApi.getInstance().retrievePhylogeneticTree(retrievePhylogeneticTreeRequestBody, { signal });\n return response.data;\n },\n queryKey: [CASEDB_QUERY_KEY.PHYLOGENETIC_TREE, JSON.stringify(retrievePhylogeneticTreeRequestBody)],\n retry: false,\n staleTime: Infinity,\n });\n\n const onOpenFiltersButtonClick = useCallback(() => {\n EventBusService.getInstance().emit('openFiltersMenu');\n }, []);\n\n const isLoading = !!treeConfiguration && (isCaseDataLoading || (hasEnoughSequencesToShowTree && isTreeLoading));\n const isTreeUnavailable = hasToManyResultsToShowTree || !isCaseDataLoading && ((!isLoading && !!treeError) || !hasEnoughSequencesToShowTree || tree?.maxBranchLength?.toNumber() === 0 || tree?.size === 0 || !treeConfiguration || (!isLoading && tree === null));\n const shouldShowTree = !hasToManyResultsToShowTree && !!treeConfiguration && !isCaseDataLoading && !treeError && !isTreeLoading && tree?.size > 0 && hasEnoughSequencesToShowTree;\n\n const widgetUnavailableReason = useMemo<ReactElement | string>(() => {\n if (!isTreeUnavailable) {\n return null;\n }\n if (!hasEnoughSequencesToShowTree) {\n return t`The phylogenetic tree cannot be displayed because there are not enough sequences available for the shown cases. A minimum of 2 sequences is required to generate a phylogenetic tree.`;\n }\n if (hasEnoughSequencesToShowTree && !tree) {\n return t`The phylogenetic tree cannot be displayed because the tree could not be generated. Either there are no sequences available for the shown cases or the selected tree configuration does not yield a valid tree.`;\n }\n if (hasToManyResultsToShowTree) {\n return (\n (\n <>\n <Box\n sx={{\n marginBottom: 2,\n }}\n >\n {t('The phylogenetic tree cannot be displayed because the number of cases ({{caseCount}}) exceeds the maximum allowed number of cases ({{maxSize}}) to display a phylogenetic tree. Refine your filters to reduce the number of results.', {\n caseCount: caseIds.length,\n maxSize: completeCaseType.props.read_max_tree_size,\n })}\n </Box>\n <Button\n color={'inherit'}\n onClick={onOpenFiltersButtonClick}\n variant={'outlined'}\n >\n {t`Refine filters`}\n </Button>\n </>\n )\n );\n }\n }, [caseIds.length, completeCaseType.props.read_max_tree_size, hasEnoughSequencesToShowTree, hasToManyResultsToShowTree, isTreeUnavailable, onOpenFiltersButtonClick, t, tree]);\n\n useEffect(() => {\n if (treeData) {\n setPhylogeneticTreeResponse(treeData);\n }\n }, [treeData, setPhylogeneticTreeResponse]);\n\n useEffect(() => {\n if (!treeConfigurations?.length) {\n setTreeConfiguration(null);\n updateWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE, {\n treeConfiguration: null,\n });\n return;\n }\n const newConfig = dashboardStore.getState().getWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE)?.treeConfiguration || treeConfigurations[0];\n\n setTreeConfiguration(newConfig);\n updateWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE, {\n treeConfiguration: newConfig,\n });\n }, [dashboardStore, treeConfigurations, updateWidgetDataPersistable]);\n\n useEffect(() => {\n if (sortByField && isTreeLinked && shouldShowTree) {\n treeRef.current?.unlink({\n positionX: 0,\n positionY: 0,\n zoomLevel: ConfigService.getInstance<CaseDbConfig>().config.tree.INITIAL_UNLINKED_ZOOM_LEVEL,\n });\n }\n }, [isTreeLinked, shouldShowTree, sortByField]);\n\n const updateEpiTreeWidgetDataDebounced = useDebouncedCallback((viewState: PhylogeneticTreeViewState) => {\n updateWidgetData<TreeWidgetData>(DASHBOARD_COMPONENT_NAME.TREE, {\n horizontalScrollPosition: viewState.horizontalScrollPosition,\n verticalScrollPosition: viewState.verticalScrollPosition,\n zoomLevel: viewState.zoomLevel,\n });\n }, 500);\n\n const onTreeViewStateChange = useCallback((viewState: PhylogeneticTreeViewState) => {\n updateEpiTreeWidgetDataDebounced(viewState);\n }, [updateEpiTreeWidgetDataDebounced]);\n\n const onTreeCanvasChange = useCallback((canvas?: HTMLCanvasElement) => {\n setTreeCanvas(canvas);\n }, []);\n\n const onEpiContextMenuClose = useCallback(() => {\n setContextMenuConfig(null);\n setZoomInMenuItemConfig(null);\n }, []);\n\n const linkLineListToTree = useCallback(() => {\n const perform = async () => {\n await setSorting(null, null);\n treeRef.current?.syncScrollToVisibleTree();\n };\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n perform();\n\n }, [setSorting]);\n\n const onLinkButtonClick = useCallback(() => {\n linkLineListToTree();\n\n }, [linkLineListToTree]);\n\n const resetZoomLevelAndScrollPosition = useCallback(() => {\n treeRef.current?.link(0);\n }, []);\n\n const onAddTreeFilterMenuItemClick = useCallback(async (onMenuClose: () => void) => {\n await addTreeFilter(zoomInMenuItemConfig.rootId);\n resetZoomLevelAndScrollPosition();\n onMenuClose();\n }, [addTreeFilter, zoomInMenuItemConfig, resetZoomLevelAndScrollPosition]);\n\n const onRemoveTreeFilterButtonClick = useCallback(async () => {\n await removeTreeFilter();\n resetZoomLevelAndScrollPosition();\n }, [removeTreeFilter, resetZoomLevelAndScrollPosition]);\n\n const onTreeFilterStepOutButtonClick = useCallback(async () => {\n await treeFilterStepOut();\n resetZoomLevelAndScrollPosition();\n }, [resetZoomLevelAndScrollPosition, treeFilterStepOut]);\n\n const onTreePathClick = useCallback(({ mouseEvent, pathProperties }: PhylogeneticTreePathClickEvent) => {\n setZoomInMenuItemConfig(null);\n setExtraLeafInfoId(null);\n\n if (pathProperties?.treeNode) {\n const { treeNode } = pathProperties;\n setContextMenuConfig({\n caseIds: treeNode.subTreeLeaveNames ? treeNode.subTreeLeaveNames : [treeNode.name],\n mouseEvent,\n position: {\n left: mouseEvent.clientX,\n top: mouseEvent.clientY,\n },\n });\n if (treeNode.subTreeNames.length && treeNode.name && treeNode.maxBranchLength.toNumber()) {\n setZoomInMenuItemConfig({\n caseIds: treeNode.subTreeLeaveNames,\n rootId: treeNode.name,\n });\n }\n if (treeNode.name && !treeNode.subTreeNames.length) {\n setExtraLeafInfoId(treeNode.name);\n }\n return;\n }\n\n if (pathProperties?.subTreeLeaveNames?.length) {\n setContextMenuConfig({\n caseIds: pathProperties.subTreeLeaveNames,\n mouseEvent,\n position: {\n left: mouseEvent.clientX,\n top: mouseEvent.clientY,\n },\n });\n }\n }, []);\n\n const onShowDetailsSelectionMenuItemClick = useCallback((onMenuClose: () => void) => {\n EventBusService.getInstance().emit('openCaseInfoDialog', {\n caseId: baseData.find(c => c.id === extraLeafInfoId).id,\n caseTypeId: completeCaseType.id,\n });\n onMenuClose();\n }, [extraLeafInfoId, baseData, completeCaseType.id]);\n\n const getEpiContextMenuExtraItems = useCallback((onMenuClose: () => void): ReactElement => {\n if (zoomInMenuItemConfig) {\n return (\n <MenuItem\n divider\n // eslint-disable-next-line @eslint-react/kit/jsx-no-bind\n onClick={async () => onAddTreeFilterMenuItemClick(onMenuClose)}\n >\n <ListItemIcon>\n <FilterAltIcon fontSize={'small'} />\n </ListItemIcon>\n <ListItemText>\n {t`Filter (show only this subtree)`}\n </ListItemText>\n </MenuItem>\n );\n }\n if (extraLeafInfoId) {\n return (\n <MenuItem\n divider\n // eslint-disable-next-line @eslint-react/kit/jsx-no-bind\n onClick={() => onShowDetailsSelectionMenuItemClick(onMenuClose)}\n >\n <ListItemIcon>\n <InfoOutlinedIcon fontSize={'small'} />\n </ListItemIcon>\n <ListItemText>\n {t`Show details`}\n </ListItemText>\n </MenuItem>\n );\n }\n }, [extraLeafInfoId, onShowDetailsSelectionMenuItemClick, onAddTreeFilterMenuItemClick, t, zoomInMenuItemConfig]);\n\n const titleMenu = useMemo<MenuItemData | string>(() => {\n if (!treeConfigurations?.length) {\n return t`Tree`;\n }\n\n const menu: MenuItemData = {\n disabled: !treeConfiguration,\n items: treeConfigurations?.map<MenuItemData>(config => ({\n active: !!treeConfiguration && treeConfiguration.computedId === config.computedId,\n callback: () => {\n const perform = async () => {\n await removeTreeFilter();\n updateWidgetDataPersistable<TreeWidgetDataPersistable>(DASHBOARD_COMPONENT_NAME.TREE, {\n treeConfiguration: config,\n });\n setTreeConfiguration(config);\n };\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n perform();\n },\n label: TreeWidgetUtil.getTreeConfigurationLabel(config),\n tooltip: (\n <TreeDescription\n treeConfiguration={config}\n />\n ),\n })) ?? [],\n label: treeConfiguration ? t('Tree: {{algorithm}}', { algorithm: TreeWidgetUtil.getTreeConfigurationLabel(treeConfiguration) }) : t`Tree`,\n tooltip: treeConfiguration\n ? (\n <TreeDescription\n treeConfiguration={treeConfiguration}\n />\n )\n : undefined,\n };\n\n return menu;\n }, [treeConfigurations, treeConfiguration, t, removeTreeFilter, updateWidgetDataPersistable]);\n\n const primaryMenu = useMemo<MenuItemData[]>(() => {\n return [\n {\n callback: onTreeFilterStepOutButtonClick,\n disabled: !hasActiveTreeFilter,\n label: t`Change tree filter to nearest ancestor`,\n leftIcon: <ArrowUpwardIcon />,\n },\n {\n callback: onRemoveTreeFilterButtonClick,\n disabled: !hasActiveTreeFilter,\n label: t`Remove tree filter`,\n leftIcon: <ClearIcon />,\n },\n {\n callback: onLinkButtonClick,\n label: t`Link and snap the Line List to the Tree (resets tree zoom level and Line List sorting)`,\n leftIcon: (\n <LinkIcon\n sx={{\n color: isTreeLinked ? undefined : theme.palette.error.main,\n }}\n />\n ),\n },\n ];\n }, [hasActiveTreeFilter, isTreeLinked, onLinkButtonClick, onRemoveTreeFilterButtonClick, onTreeFilterStepOutButtonClick, t, theme.palette.error.main]);\n\n useEffect(() => {\n const emitDownloadOptions = () => {\n const baseName = t('Phylogenetic Tree - {{geneticDistanceProtocol}} - {{treeAlgorithm}}',\n {\n geneticDistanceProtocol: treeConfiguration?.geneticDistanceProtocol.name ?? '',\n treeAlgorithm: treeConfiguration?.treeAlgorithm.name ?? '',\n });\n\n EventBusService.getInstance().emit('onDownloadOptionsChanged', {\n disabled: isTreeUnavailable,\n items: [\n {\n callback: () => CaseDbDownloadUtil.downloadNewick(baseName, newick, completeCaseType, t),\n label: t`Save as Newick`,\n },\n {\n callback: () => CaseDbDownloadUtil.downloadCanvasImage(baseName, treeCanvas, 'jpeg', completeCaseType, t),\n label: t`Save as JPEG`,\n },\n {\n callback: () => CaseDbDownloadUtil.downloadCanvasImage(baseName, treeCanvas, 'png', completeCaseType, t),\n label: t`Save as PNG`,\n },\n ],\n zone: DASHBOARD_COMPONENT_NAME.TREE,\n zoneLabel: t`Phylogenetic Tree`,\n });\n };\n\n\n emitDownloadOptions();\n const eventBusService = EventBusService.getInstance();\n eventBusService.addEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n\n return () => {\n eventBusService.emit('onDownloadOptionsChanged', {\n items: null,\n zone: DASHBOARD_COMPONENT_NAME.TREE,\n zoneLabel: t`Tree`,\n });\n eventBusService.removeEventListener('onDownloadOptionsRequested', emitDownloadOptions);\n };\n }, [completeCaseType, isTreeLinked, isTreeUnavailable, newick, t, treeCanvas, treeConfiguration?.geneticDistanceProtocol.name, treeConfiguration?.treeAlgorithm.name]);\n\n\n useEffect(() => {\n // eslint-disable-next-line @eslint-react/web-api-no-leaked-event-listener\n const removeEventListener = EventBusService.getInstance().addEventListener('onLinkLineListAndTree', () => treeRef.current?.link());\n return () => {\n removeEventListener();\n };\n }, []);\n\n\n const onLinkStateChange = useCallback((linked: boolean) => {\n setIsTreeLinked(linked);\n }, [setIsTreeLinked]);\n\n const phylogeneticTreeHighlightedNodeNamesSubject = useMemo(() => {\n const subject = new Subject<PhylogeneticTreeHighlightedNodeNamesSubjectValue>({\n highlightedNodeNames: dashboardContext.highlightSubject.data.caseIds,\n persistedHighlightedNodeNames: [],\n });\n return subject;\n }, [dashboardContext.highlightSubject.data.caseIds]);\n\n useEffect(() => {\n const unsubscribe = phylogeneticTreeHighlightedNodeNamesSubject.subscribe((data) => {\n dashboardContext.highlight({\n caseIds: data.highlightedNodeNames,\n origin: DASHBOARD_COMPONENT_NAME.TREE,\n });\n });\n return () => {\n unsubscribe();\n };\n }, [phylogeneticTreeHighlightedNodeNamesSubject, dashboardContext]);\n\n useEffect(() => {\n const unsubscribe = dashboardContext.highlightSubject.subscribe(data => {\n if (data.origin === DASHBOARD_COMPONENT_NAME.TREE) {\n return;\n }\n phylogeneticTreeHighlightedNodeNamesSubject.next({\n highlightedNodeNames: data.caseIds,\n persistedHighlightedNodeNames: [],\n });\n });\n return () => {\n unsubscribe();\n };\n }, [dashboardContext.highlightSubject, phylogeneticTreeHighlightedNodeNamesSubject]);\n\n return (\n <DashboardWidget\n expandDisabled={isTreeUnavailable}\n primaryMenu={primaryMenu}\n title={titleMenu}\n widgetName={DASHBOARD_COMPONENT_NAME.TREE}\n >\n <Box\n sx={{\n height: '100%',\n overflow: 'clip',\n position: 'relative',\n width: '100%',\n }}\n >\n {isTreeUnavailable && (\n <>\n <WidgetUnavailable\n reason={widgetUnavailableReason}\n widgetLabel={t`phylogenetic tree`}\n />\n </>\n )}\n {(isLoading && !isTreeUnavailable) && (\n <Spinner\n label={t`Loading`}\n takingLongerTimeoutMs={ConfigService.getInstance<CaseDbConfig>().config.tree.TAKING_LONGER_TIMEOUT_MS}\n />\n )}\n {!isTreeUnavailable && shouldShowTree && (\n <PhylogeneticTree\n ancestorDotRadius={treeConfig.ANCESTOR_DOT_RADIUS}\n ariaLabel={treeCanvasAriaLabel}\n backgroundColor={theme.palette.background.paper}\n dimFn={theme['gen-epix-ui-client-casedb'].tree.dimFn}\n fontFamily={theme.typography.fontFamily}\n headerHeight={treeConfig.HEADER_HEIGHT}\n highlightedNodeNamesSubject={phylogeneticTreeHighlightedNodeNamesSubject}\n initialViewState={initialTreeViewState}\n itemHeight={ConfigService.getInstance<CaseDbConfig>().config.lineList.TABLE_ROW_HEIGHT}\n leafDotRadius={treeConfig.LEAF_DOT_RADIUS}\n linkedScrollDebounceDelayMs={treeConfig.LINKED_SCROLL_DEBOUNCE_DELAY_MS}\n maxScaleWidthPx={treeConfig.MAX_SCALE_WIDTH_PX}\n maxZoomLevel={treeConfig.MAX_ZOOM_LEVEL}\n maxZoomSpeed={treeConfig.MAX_ZOOM_SPEED}\n minimumDistancePercentageToShowLabel={treeConfig.MINIMUM_DISTANCE_PERCENTAGE_TO_SHOW_LABEL}\n minScaleWidthPx={treeConfig.MIN_SCALE_WIDTH_PX}\n minZoomLevel={treeConfig.MIN_ZOOM_LEVEL}\n minZoomSpeed={treeConfig.MIN_ZOOM_SPEED}\n nodeNameColors={stratification?.caseIdColors}\n onCanvasChange={onTreeCanvasChange}\n onLinkStateChange={onLinkStateChange}\n onPathClick={onTreePathClick}\n onViewStateChange={onTreeViewStateChange}\n panningThreshold={treeConfig.PANNING_THRESHOLD}\n ref={treeRef}\n regularFillColorSupportLine={treeConfig.REGULAR_FILL_COLOR_SUPPORT_LINE}\n scaleColor={theme.palette.text.primary}\n scaleIncrements={treeConfig.SCALE_INCREMENTS}\n scrollSubject={dashboardContext.linkedScrollSubject}\n shouldShowDistances={isShowDistancesEnabled}\n shouldShowSupportLinesWhenUnlinked={isShowSupportLinesWhenUnlinkedEnabled}\n sortedLeafNames={sortedLeafNames}\n supportLineColorLinked={theme['gen-epix-ui-client-casedb'].tree.supportLineColorLinked}\n supportLineColorUnlinked={theme['gen-epix-ui-client-casedb'].tree.supportLineColorUnlinked}\n tree={tree}\n treeColor={theme['gen-epix-ui-client-casedb'].tree.color}\n treeFont={theme['gen-epix-ui-client-casedb'].tree.font}\n treePadding={treeConfig.TREE_PADDING}\n visibleRangeSubject={dashboardContext.lineListRangeSubject}\n />\n )}\n </Box>\n <ContextMenu\n config={contextMenuConfig}\n getExtraItems={getEpiContextMenuExtraItems}\n onMenuClose={onEpiContextMenuClose}\n />\n </DashboardWidget>\n );\n};\n"],"mappings":"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"}
|