Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 47 additions & 24 deletions src/components/datatable/BottomPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { IconCross16 } from '@dhis2/ui'
import React, { useRef, useCallback } from 'react'
import React, {
useRef,
useCallback,
useState,
useEffect,
useLayoutEffect,
} from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { closeDataTable, resizeDataTable } from '../../actions/dataTable.js'
import {
LAYERS_PANEL_WIDTH,
RIGHT_PANEL_WIDTH,
} from '../../constants/layout.js'
import useKeyDown from '../../hooks/useKeyDown.js'
import { getCssVar } from '../../util/helpers.js'
import { useWindowDimensions } from '../WindowDimensionsProvider.jsx'
Expand All @@ -17,34 +19,56 @@ import styles from './styles/BottomPanel.module.css'
// Container for DataTable
const BottomPanel = () => {
const dataTableHeight = useSelector((state) => state.ui.dataTableHeight)
const layersPanelOpen = useSelector((state) => state.ui.layersPanelOpen)
const rightPanelOpen = useSelector((state) => state.ui.rightPanelOpen)

const dispatch = useDispatch()
const { width, height } = useWindowDimensions()
const { height } = useWindowDimensions()
const panelRef = useRef(null)

const onResize = useCallback(
(h) => (panelRef.current.style.height = `${h}px`),
[panelRef]
)
const [panelWidth, setPanelWidth] = useState(0)

const maxHeight =
height - getCssVar('--header-height') - getCssVar('--toolbar-height')
const tableHeight =
dataTableHeight < maxHeight ? dataTableHeight : maxHeight
const layersWidth = layersPanelOpen ? LAYERS_PANEL_WIDTH : 0
const rightPanelWidth = rightPanelOpen ? RIGHT_PANEL_WIDTH : 0
const tableWidth = width - layersWidth - rightPanelWidth
const dataTableControlsHeight = 20
const onResize = useCallback((h) => {
document.documentElement.style.setProperty(
'--data-table-height',
`${h}px`
)
}, [])

useLayoutEffect(() => {
document.documentElement.style.setProperty(
'--data-table-height',
`${tableHeight}px`
)
}, [tableHeight])

useLayoutEffect(
() => () =>
document.documentElement.style.removeProperty(
'--data-table-height'
),
[]
)

useEffect(() => {
const observer = new ResizeObserver(() => {
if (panelRef.current) {
setPanelWidth(panelRef.current.getBoundingClientRect().width)
}
})
if (panelRef.current) {
observer.observe(panelRef.current)
}
return () => observer.disconnect()
}, [])

useKeyDown('Escape', () => dispatch(closeDataTable()), true)

return (
<div
ref={panelRef}
className={styles.bottomPanel}
style={{ height: tableHeight, width: tableWidth }}
data-test="bottom-panel"
>
<div className={styles.dataTableControls}>
Expand All @@ -60,12 +84,11 @@ const BottomPanel = () => {
<IconCross16 />
</button>
</div>
<ErrorBoundary>
<DataTable
availableHeight={dataTableHeight - dataTableControlsHeight}
availableWidth={tableWidth}
/>
</ErrorBoundary>
<div className={styles.tableContainer}>
<ErrorBoundary>
<DataTable availableWidth={panelWidth} />
</ErrorBoundary>
</div>
</div>
)
}
Expand Down
44 changes: 30 additions & 14 deletions src/components/datatable/DataTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,14 @@ const TableComponents = {
),
}

const Table = ({ availableHeight, availableWidth }) => {
const Table = ({ availableWidth }) => {
const {
systemSettings: { keyAnalysisDigitGroupSeparator },
} = useCachedData()

const headerRowRef = useRef(null)
const [columnWidths, setColumnWidths] = useState([])
const minColumnWidthsRef = useRef([])
const { mapViews } = useSelector((state) => state.map)
const activeLayerId = useSelector((state) => state.dataTable)

Expand Down Expand Up @@ -206,12 +207,7 @@ const Table = ({ availableHeight, availableWidth }) => {
})

useEffect(() => {
/* The combination of automatic table layout and virtual scrolling
* causes a content shift when scrolling and filtering because the
* cells in the DOM have a different content length which causes the
* columns to have a different width. To avoid that we measure the
* initial column widths and switch to a fixed layout based on these
* measured widths */
// Measure column widths in auto layout, then switch to fixed to prevent content shift during virtual scrolling
if (columnWidths.length === 0 && headerRowRef.current) {
requestAnimationFrame(() => {
const measuredColumnWidths = []
Expand All @@ -221,20 +217,41 @@ const Table = ({ availableHeight, availableWidth }) => {
measuredColumnWidths.push(Math.floor(rect.width))
}

minColumnWidthsRef.current = measuredColumnWidths
setColumnWidths(measuredColumnWidths)
})
}
}, [columnWidths])

useEffect(() => {
/* When the window is resized, the sidebar opens, or the table
* headers change, the table needs to switch back to its
* automatic layout so that the cells can subsequently can be
* measured again in the useEffect hook above */
// Reset to auto layout for re-measurement when headers change
if (!error) {
minColumnWidthsRef.current = []
setColumnWidths([])
}
}, [availableWidth, headers, error])
}, [headers, error])

useEffect(() => {
// Scale column widths proportionally on resize, clamped to initial measured widths
if (!error) {
setColumnWidths((prev) => {
if (prev.length === 0) {
return prev
}
const prevTotal = prev.reduce((sum, w) => sum + w, 0)
if (prevTotal === 0 || availableWidth === 0) {
return []
}
const minWidths = minColumnWidthsRef.current
return prev.map((w, i) =>
Math.max(
minWidths[i] ?? 0,
Math.round((w / prevTotal) * availableWidth)
)
)
})
}
}, [availableWidth, error])

if (error) {
return <p className={styles.noSupport}>{error}</p>
Expand All @@ -246,7 +263,7 @@ const Table = ({ availableHeight, availableWidth }) => {
context={tableContext}
components={TableComponents}
style={{
height: availableHeight,
height: '100%',
width: '100%',
}}
data={rows}
Expand Down Expand Up @@ -321,7 +338,6 @@ const Table = ({ availableHeight, availableWidth }) => {
}

Table.propTypes = {
availableHeight: PropTypes.number,
availableWidth: PropTypes.number,
}

Expand Down
10 changes: 10 additions & 0 deletions src/components/datatable/styles/BottomPanel.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: var(--data-table-height);
z-index: 1040;
background: #fff;
display: flex;
flex-direction: column;
}

.tableContainer {
flex: 1;
min-height: 0;
position: relative;
}

.dataTableControls {
Expand Down
45 changes: 35 additions & 10 deletions src/components/download/OverviewMapOutline.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types'
import { useState, useCallback, useEffect } from 'react'
import { useState, useRef, useCallback, useEffect } from 'react'
import { GEOJSON_LAYER } from '../../constants/layers.js'
import { getCssColor } from '../../util/colors.js'
import { getCoordinatesBounds } from '../../util/geojson.js'

const layerId = 'overview-outline'
Expand Down Expand Up @@ -30,7 +31,8 @@ const getMapOutline = (map) => {

const OverviewMapOutline = ({ mainMap, overviewMap, isDark = false }) => {
const [outline, setOutline] = useState(getMapOutline(mainMap))
const [sourceId, setSourceId] = useState()
const sourceIdRef = useRef()
const layerRef = useRef()

const onMainMapMove = useCallback(() => {
setOutline(getMapOutline(mainMap))
Expand All @@ -45,27 +47,40 @@ const OverviewMapOutline = ({ mainMap, overviewMap, isDark = false }) => {

useEffect(() => {
if (outline) {
const strokeColor = isDark
? getCssColor('--colors-grey300')
: getCssColor('--colors-grey900')
const config = {
type: GEOJSON_LAYER,
id: layerId,
index: 1,
data: [outline],
style: {
color: 'transparent',
strokeColor: isDark ? 'orange' : '#333',
strokeColor,
weight: 3,
},
}

if (!sourceId) {
const layer = overviewMap.createLayer(config)
overviewMap.addLayer(layer)
setSourceId(layer.getId())
} else {
const source = overviewMap.getMapGL().getSource(sourceId)
if (sourceIdRef.current) {
const mapGl = overviewMap.getMapGL()
const source = mapGl.getSource(sourceIdRef.current)
if (source) {
source.setData(outline)
}
const outlineLayerId = `${sourceIdRef.current}-outline`
if (mapGl.getLayer(outlineLayerId)) {
mapGl.setPaintProperty(
outlineLayerId,
'line-color',
strokeColor
)
}
} else {
const layer = overviewMap.createLayer(config)
overviewMap.addLayer(layer)
sourceIdRef.current = layer.getId()
layerRef.current = layer
}

// Make sure outline bounds is inside overview map bounds
Expand All @@ -83,7 +98,17 @@ const OverviewMapOutline = ({ mainMap, overviewMap, isDark = false }) => {
overviewMap.getMapGL().fitBounds(outlineBounds, { padding: 80 })
}
}
}, [overviewMap, outline, sourceId, isDark])
}, [overviewMap, outline, isDark])

useEffect(() => {
return () => {
if (layerRef.current) {
overviewMap.removeLayer(layerRef.current)
layerRef.current = undefined
sourceIdRef.current = undefined
}
}
}, [overviewMap])

return null
}
Expand Down
Loading
Loading