Enhance dynamic height handling for RunDetails components.

Added support for dynamic height calculation in RunDetails and integrated it with ResultGrid to adjust detail panel heights based on content changes. This ensures proper rendering and improves responsiveness of the UI.
This commit is contained in:
GotthardG 2025-03-05 11:13:20 +01:00
parent 6bd4843d38
commit 91aebae473
2 changed files with 190 additions and 114 deletions

View File

@ -102,6 +102,8 @@ interface ResultGridProps {
const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => { const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
const [rows, setRows] = useState<TreeRow[]>([]); const [rows, setRows] = useState<TreeRow[]>([]);
const [basePath, setBasePath] = useState(''); const [basePath, setBasePath] = useState('');
const [detailPanelHeights, setDetailPanelHeights] = useState<{ [key: string]: number }>({}); // Store dynamic heights
const hasProcessingResults = (row: TreeRow): boolean => { const hasProcessingResults = (row: TreeRow): boolean => {
// You can later replace this placeholder with actual logic. // You can later replace this placeholder with actual logic.
// Mocking the logic by returning `true` for demonstration. // Mocking the logic by returning `true` for demonstration.
@ -282,19 +284,38 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
}, },
]; ];
const handleDetailPanelHeightChange = (rowId: string, height: number) => {
// Update the height of the specific detail panel dynamically
setDetailPanelHeights((prev) => {
if (prev[rowId] !== height) {
return { ...prev, [rowId]: height };
}
return prev;
});
};
const getDetailPanelContent = (params: any) => { const getDetailPanelContent = (params: any) => {
if (params.row.type === 'run') { if (params.row.type === 'run') {
return <RunDetails run={params.row} />; return (
<RunDetails
run={params.row}
onHeightChange={(height: number) => handleDetailPanelHeightChange(params.row.id, height)} // Pass callback for dynamic height
/>
);
} }
return null; return null;
}; };
const getDetailPanelHeight = (params: any) => { const getDetailPanelHeight = (params: any) => {
if (params.row.type === 'run') return 300; if (params.row.type === 'run') {
// Use the dynamically calculated height from state
return detailPanelHeights[params.row.id] || 600; // Fallback to default height if not yet calculated
}
return 0; return 0;
}; };
return ( return (
<DataGridPremium <DataGridPremium
rows={rows} rows={rows}

View File

@ -1,120 +1,175 @@
import React from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { import {
Accordion, Accordion,
AccordionSummary, AccordionSummary,
AccordionDetails, AccordionDetails,
Typography, Typography,
} from '@mui/material'; Grid,
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import './SampleImage.css';
const RunDetails: React.FC<RunDetailsProps> = ({ run }) => { interface RunDetailsProps {
const { beamline_parameters } = run; run: ExperimentParameters;
const { synchrotron, beamline, detector } = beamline_parameters; onHeightChange?: (height: number) => void; // Callback to notify the parent about height changes
}
return ( const RunDetails: React.FC<RunDetailsProps> = ({ run, onHeightChange }) => {
<div style={{ padding: '16px', border: '1px solid #ccc', borderRadius: '4px' }}> const containerRef = useRef<HTMLDivElement | null>(null); // Ref to track component height
<Typography variant="h6" gutterBottom> const [currentHeight, setCurrentHeight] = useState<number>(0);
Run {run.run_number} Details
</Typography>
<Typography variant="subtitle1" gutterBottom>
Beamline: {beamline} | Synchrotron: {synchrotron}
</Typography>
<Accordion> const { beamline_parameters, images } = run;
<AccordionSummary const { synchrotron, beamline, detector } = beamline_parameters;
expandIcon={<ExpandMoreIcon />}
aria-controls="detector-content"
id="detector-header"
>
<Typography><strong>Detector Details</strong></Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>Manufacturer: {detector?.manufacturer || 'N/A'}</Typography>
<Typography>Model: {detector?.model || 'N/A'}</Typography>
<Typography>Type: {detector?.type || 'N/A'}</Typography>
<Typography>
Beam Center (px): x: {detector?.beamCenterX_px || 'N/A'}, y: {detector?.beamCenterY_px || 'N/A'}
</Typography>
</AccordionDetails>
</Accordion>
<Accordion> // Calculate and notify the parent about height changes
<AccordionSummary const updateHeight = () => {
expandIcon={<ExpandMoreIcon />} if (containerRef.current) {
aria-controls="beamline-content" const newHeight = containerRef.current.offsetHeight;
id="beamline-header" if (newHeight !== currentHeight) {
> setCurrentHeight(newHeight);
<Typography><strong>Beamline Details</strong></Typography> if (onHeightChange) {
</AccordionSummary> onHeightChange(newHeight);
<AccordionDetails> }
<Typography>Synchrotron: {beamline_parameters?.synchrotron || 'N/A'}</Typography> }
<Typography>Ring mode: {beamline_parameters?.ringMode || 'N/A'}</Typography> }
<Typography>Ring current: {beamline_parameters?.ringCurrent_A || 'N/A'}</Typography> };
<Typography>Beamline: {beamline_parameters?.beamline || 'N/A'}</Typography>
<Typography>Undulator: {beamline_parameters?.undulator || 'N/A'}</Typography>
<Typography>Undulator gap: {beamline_parameters?.undulatorgap_mm || 'N/A'}</Typography>
<Typography>Focusing optic: {beamline_parameters?.focusingOptic || 'N/A'}</Typography>
<Typography>Monochromator: {beamline_parameters?.monochromator || 'N/A'}</Typography>
</AccordionDetails>
</Accordion>
<Accordion> useEffect(() => {
<AccordionSummary updateHeight(); // Update height on initial render
expandIcon={<ExpandMoreIcon />} }, []);
aria-controls="beam-content"
id="beam-header"
>
<Typography><strong>Beam characteristics</strong></Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>Wavelength: {beamline_parameters?.wavelength || 'N/A'}</Typography>
<Typography>Energy: {beamline_parameters?.energy || 'N/A'}</Typography>
<Typography>Transmission: {beamline_parameters?.transmission || 'N/A'}</Typography>
<Typography>Beam focus (µm): vertical: {beamline_parameters?.beamSizeHeight || 'N/A'} , horizontal: {beamline_parameters?.beamSizeWidth || 'N/A'}</Typography>
<Typography>Flux at sample (ph/s): {beamline_parameters?.beamlineFluxAtSample_ph_s || 'N/A'}</Typography>
</AccordionDetails>
</Accordion>
<Accordion> useEffect(() => {
<AccordionSummary // Update height whenever the component content changes
expandIcon={<ExpandMoreIcon />} const observer = new ResizeObserver(updateHeight);
aria-controls="sample-content" if (containerRef.current) {
id="sample-header" observer.observe(containerRef.current);
> }
<Typography><strong>Sample environment</strong></Typography> return () => {
</AccordionSummary> observer.disconnect();
<AccordionDetails> };
<Typography>Cryojet temperature (K): {beamline_parameters?.cryojetTemperature_K || 'N/A'}</Typography> }, [containerRef]);
<Typography>Humidifier temperature (K): {beamline_parameters?.humidifierTemperature_K || 'N/A'}</Typography>
<Typography>Humidifier humidity (%): {beamline_parameters?.humidifierHumidity || 'N/A'}</Typography>
</AccordionDetails>
</Accordion>
<Accordion> return (
<AccordionSummary <div
expandIcon={<ExpandMoreIcon />} ref={containerRef} // Attach the ref to the main container
aria-controls="images-content" style={{
id="images-header" display: 'flex',
> gap: '16px',
<Typography><strong>Associated Images</strong></Typography> padding: '16px',
</AccordionSummary> border: '1px solid #ccc',
<AccordionDetails> borderRadius: '4px',
{run.images?.map((img) => ( alignItems: 'flex-start',
<img }}
key={img.id} >
src={img.filepath} {/* Main Details Section */}
alt={img.comment || 'Sample Image'} <div style={{ flexGrow: 1 }}>
style={{ <Typography variant="h6" gutterBottom>
width: '100%', Run {run.run_number} Details
border: '1px solid #ccc', </Typography>
marginTop: 8, <Typography variant="subtitle1" gutterBottom>
}} Beamline: {beamline} | Synchrotron: {synchrotron}
/> </Typography>
)) || 'No Images Available'}
</AccordionDetails>
</Accordion>
</div>
);
};
export default RunDetails; {/* Detector Details Accordion */}
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="detector-content"
id="detector-header"
>
<Typography>
<strong>Detector Details</strong>
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>Manufacturer: {detector?.manufacturer || 'N/A'}</Typography>
<Typography>Model: {detector?.model || 'N/A'}</Typography>
<Typography>Type: {detector?.type || 'N/A'}</Typography>
<Typography>
Beam Center (px): x: {detector?.beamCenterX_px || 'N/A'}, y: {detector?.beamCenterY_px || 'N/A'}
</Typography>
</AccordionDetails>
</Accordion>
{/* Beamline Details Accordion */}
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="beamline-content"
id="beamline-header"
>
<Typography>
<strong>Beamline Details</strong>
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>Synchrotron: {beamline_parameters?.synchrotron || 'N/A'}</Typography>
<Typography>Ring mode: {beamline_parameters?.ringMode || 'N/A'}</Typography>
<Typography>Ring current: {beamline_parameters?.ringCurrent_A || 'N/A'}</Typography>
<Typography>Beamline: {beamline_parameters?.beamline || 'N/A'}</Typography>
<Typography>Undulator: {beamline_parameters?.undulator || 'N/A'}</Typography>
<Typography>Undulator gap: {beamline_parameters?.undulatorgap_mm || 'N/A'}</Typography>
<Typography>Focusing optic: {beamline_parameters?.focusingOptic || 'N/A'}</Typography>
<Typography>Monochromator: {beamline_parameters?.monochromator || 'N/A'}</Typography>
</AccordionDetails>
</Accordion>
{/* Beam Characteristics Accordion */}
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="beam-content"
id="beam-header"
>
<Typography>
<strong>Beam Characteristics</strong>
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>Wavelength: {beamline_parameters?.wavelength || 'N/A'}</Typography>
<Typography>Energy: {beamline_parameters?.energy || 'N/A'}</Typography>
<Typography>Transmission: {beamline_parameters?.transmission || 'N/A'}</Typography>
<Typography>
Beam focus (µm): vertical: {beamline_parameters?.beamSizeHeight || 'N/A'}, horizontal:{' '}
{beamline_parameters?.beamSizeWidth || 'N/A'}
</Typography>
<Typography>Flux at sample (ph/s): {beamline_parameters?.beamlineFluxAtSample_ph_s || 'N/A'}</Typography>
</AccordionDetails>
</Accordion>
</div>
{/* Image Section */}
<div style={{ width: '900px' }}>
<Typography variant="h6" gutterBottom>
Associated Images
</Typography>
{images && images.length > 0 ? (
<Grid container spacing={2}>
{images.map((img) => (
<Grid item xs={6} key={img.id}>
<div className="image-container">
<img
src={img.filepath}
alt={img.comment || 'Image'}
className="zoom-image"
style={{
width: '100%',
maxWidth: '100%',
borderRadius: '4px',
}}
/>
</div>
</Grid>
))}
</Grid>
) : (
<Typography variant="body2" color="textSecondary">
No images available.
</Typography>
)}
</div>
</div>
);
};
export default RunDetails;