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
40 changes: 31 additions & 9 deletions src/components/map/MapPosition.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import cx from 'classnames'
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect, useRef } from 'react'
import { useSelector } from 'react-redux'
import { getSplitViewLayer } from '../../util/helpers.js'
import DownloadMapInfo from '../download/DownloadMapInfo.jsx'
import NorthArrow from '../download/NorthArrow.jsx'
import MapContainer from '../map/MapContainer.jsx'
import styles from './styles/MapPosition.module.css'

const incrementCount = (c) => c + 1

const MapPosition = () => {
const [map, setMap] = useState()
const [resizeCount, setResizeCount] = useState(0)
const outerRef = useRef(null)
const {
showName,
showDescription,
Expand All @@ -34,7 +37,7 @@ const MapPosition = () => {

// Trigger map resize when panels are expanded, collapsed or dragged
useEffect(() => {
setResizeCount((count) => count + 1)
setResizeCount(incrementCount)
}, [
dataTableOpen,
dataTableHeight,
Expand All @@ -43,6 +46,30 @@ const MapPosition = () => {
rightPanelOpen,
])

// Trigger map resize continuously during ResizeHandle drag (CSS variable drives height, not Redux)
useEffect(() => {
if (!outerRef.current) {
return
}
let frameId = null
const observer = new ResizeObserver(() => {
if (frameId !== null) {
return
}
frameId = requestAnimationFrame(() => {
setResizeCount(incrementCount)
frameId = null
})
})
observer.observe(outerRef.current)
return () => {
observer.disconnect()
if (frameId !== null) {
cancelAnimationFrame(frameId)
}
}
}, [])

// Reset bearing and pitch when new map (mapId changed)
useEffect(() => {
if (map) {
Expand Down Expand Up @@ -77,17 +104,12 @@ const MapPosition = () => {

return (
<div
ref={outerRef}
className={cx(styles.mapDefault, {
[styles.mapDownload]: downloadMode,
[styles.downloadMapInfoOpen]: downloadMapInfoOpen,
[styles.mapWithDataTable]: dataTableOpen,
})}
style={
dataTableOpen
? {
height: `calc(100vh - var(--header-height) - var(--toolbar-height) - ${dataTableHeight}px)`,
}
: {}
}
>
<div
id="dhis2-map-container"
Expand Down
7 changes: 7 additions & 0 deletions src/components/map/styles/MapPosition.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
height: calc(100vh - var(--header-height) - var(--toolbar-height));
}

.mapWithDataTable {
height: calc(
100vh - var(--header-height) - var(--toolbar-height) -
var(--data-table-height)
);
}

.mapDownload {
height: calc(100vh - var(--downloadheader-height));
border: var(--spacers-dp24) solid #fff;
Expand Down
Loading