Update dependencies and migrate to Node 18 minimum support
Upgraded multiple package versions, including `@esbuild` and dependencies like `@mui/x-data-grid-premium`. Adjusted `node` engine requirement to `>=18` for compatibility. This ensures modernization and alignment with current toolchain standards.
This commit is contained in:
646
frontend/package-lock.json
generated
646
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,8 @@
|
||||
"@mui/icons-material": "^6.1.5",
|
||||
"@mui/material": "^6.1.6",
|
||||
"@mui/system": "^6.1.6",
|
||||
"@mui/x-data-grid": "^7.23.3",
|
||||
"@mui/x-data-grid-premium": "^7.27.2",
|
||||
"@mui/x-tree-view": "^7.26.0",
|
||||
"axios": "^1.7.7",
|
||||
"chokidar": "^4.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
@ -59,7 +60,7 @@
|
||||
"openapi-typescript": "^7.4.2",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.7.0",
|
||||
"vite": "^5.4.8",
|
||||
"vite": "^6.2.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,131 @@
|
||||
// TypeScript (ResultGrid.tsx snippet)
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
||||
import { DataGridPremium, GridColDef } from '@mui/x-data-grid-premium';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import { OpenAPI, SamplesService } from '../../openapi';
|
||||
import './SampleImage.css';
|
||||
|
||||
|
||||
// Extend your image info interface if needed.
|
||||
interface ImageInfo {
|
||||
id: number;
|
||||
filepath: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
// This represents an experiment run as returned by your API.
|
||||
interface ExperimentParameters {
|
||||
id: number;
|
||||
run_number: number;
|
||||
sample_id: number;
|
||||
beamline_parameters: {
|
||||
beamSizeHeight: number;
|
||||
beamSizeWidth: number;
|
||||
beamline: string;
|
||||
beamlineFluxAtSample_ph_s: number;
|
||||
cryojetTemperature_K?: number | null;
|
||||
detector: {
|
||||
beamCenterX_px: number;
|
||||
beamCenterY_px: number;
|
||||
detectorDistance_mm: number;
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
pixelSizeX_um: number;
|
||||
pixelSizeY_um: number;
|
||||
serialNumber: string;
|
||||
type: string;
|
||||
};
|
||||
rotation?: {
|
||||
chi: number;
|
||||
exposureTime_s: number;
|
||||
numberOfImages: number | null;
|
||||
omegaStart_deg: number;
|
||||
omegaStep: number;
|
||||
phi: number;
|
||||
} | null;
|
||||
gridScan?: {
|
||||
xStart: number;
|
||||
xStep: number;
|
||||
yStart: number;
|
||||
yStep: number;
|
||||
zStart: number;
|
||||
zStep: number;
|
||||
numberOfImages: number;
|
||||
exposureTime_s: number;
|
||||
} | null;
|
||||
focusingOptic?: string;
|
||||
humidifierHumidity?: number | null;
|
||||
humidifierTemperature_K?: number | null;
|
||||
jet?: any;
|
||||
monochromator?: string;
|
||||
ringCurrent_A?: number;
|
||||
ringMode?: string;
|
||||
synchrotron?: string;
|
||||
transmission?: number;
|
||||
undulator?: any;
|
||||
undulatorgap_mm?: any;
|
||||
wavelength?: number;
|
||||
};
|
||||
}
|
||||
|
||||
// Represents a sample returned by the API.
|
||||
interface SampleResult {
|
||||
sample_id: number;
|
||||
sample_name: string;
|
||||
puck_name?: string;
|
||||
dewar_name?: string;
|
||||
images: ImageInfo[];
|
||||
experiment_runs?: ExperimentParameters[];
|
||||
}
|
||||
|
||||
// A flat tree row type for the grid.
|
||||
interface TreeRow {
|
||||
id: string;
|
||||
hierarchy: (string | number)[];
|
||||
type: 'sample' | 'run';
|
||||
sample_id: number;
|
||||
sample_name?: string;
|
||||
puck_name?: string;
|
||||
dewar_name?: string;
|
||||
images?: ImageInfo[];
|
||||
run_number?: number;
|
||||
beamline_parameters?: ExperimentParameters['beamline_parameters'];
|
||||
experimentType?: string;
|
||||
numberOfImages?: number;
|
||||
}
|
||||
|
||||
interface ResultGridProps {
|
||||
activePgroup: string;
|
||||
}
|
||||
|
||||
// Helper function to safely get the number of images.
|
||||
const getNumberOfImages = (run: ExperimentParameters): number => {
|
||||
const params = run.beamline_parameters;
|
||||
if (params.rotation && params.rotation.numberOfImages != null) {
|
||||
return params.rotation.numberOfImages;
|
||||
} else if (params.gridScan && params.gridScan.numberOfImages != null) {
|
||||
return params.gridScan.numberOfImages;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Helper function to determine the experiment type.
|
||||
const getExperimentType = (run: ExperimentParameters): string => {
|
||||
const params = run.beamline_parameters;
|
||||
if (params.rotation && params.rotation.numberOfImages != null && params.rotation.omegaStep != null) {
|
||||
const numImages = params.rotation.numberOfImages;
|
||||
const omegaStep = params.rotation.omegaStep;
|
||||
if ([1, 2, 4].includes(numImages) && omegaStep === 90) {
|
||||
return "Characterization";
|
||||
}
|
||||
return "Rotation";
|
||||
} else if (params.gridScan) {
|
||||
return "Grid Scan";
|
||||
}
|
||||
return "Rotation";
|
||||
};
|
||||
|
||||
const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
|
||||
const [rows, setRows] = useState<SampleResult[]>([]);
|
||||
const [rows, setRows] = useState<TreeRow[]>([]);
|
||||
const [basePath, setBasePath] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@ -44,99 +145,124 @@ const ResultGrid: React.FC<ResultGridProps> = ({ activePgroup }) => {
|
||||
console.log('Environment Mode:', mode);
|
||||
console.log('Resolved OpenAPI.BASE:', OpenAPI.BASE);
|
||||
|
||||
// Update the base path for images
|
||||
setBasePath(`${OpenAPI.BASE}/`);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = () => {
|
||||
console.log("Fetching sample results for active_pgroup:", activePgroup);
|
||||
SamplesService.getSampleResultsSamplesResultsGet(activePgroup)
|
||||
.then((response: SampleResult[]) => {
|
||||
console.log("Response received:", response);
|
||||
setRows(response);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.error("Error fetching sample results:", err);
|
||||
console.log('Fetching sample results for active_pgroup:', activePgroup);
|
||||
SamplesService.getSampleResultsSamplesResultsGet(activePgroup)
|
||||
.then((response: SampleResult[]) => {
|
||||
console.log('Response received:', response);
|
||||
const treeRows: TreeRow[] = [];
|
||||
|
||||
response.forEach((sample) => {
|
||||
// Add the sample row.
|
||||
const sampleRow: TreeRow = {
|
||||
id: `sample-${sample.sample_id}`,
|
||||
hierarchy: [sample.sample_id],
|
||||
type: 'sample',
|
||||
sample_id: sample.sample_id,
|
||||
sample_name: sample.sample_name,
|
||||
puck_name: sample.puck_name,
|
||||
dewar_name: sample.dewar_name,
|
||||
images: sample.images,
|
||||
};
|
||||
treeRows.push(sampleRow);
|
||||
|
||||
// Add experiment run rows.
|
||||
if (sample.experiment_runs) {
|
||||
sample.experiment_runs.forEach((run) => {
|
||||
const experimentType = getExperimentType(run);
|
||||
const numImages = getNumberOfImages(run);
|
||||
const runRow: TreeRow = {
|
||||
id: `run-${sample.sample_id}-${run.run_number}`,
|
||||
hierarchy: [sample.sample_id, run.run_number],
|
||||
type: 'run',
|
||||
sample_id: sample.sample_id,
|
||||
run_number: run.run_number,
|
||||
beamline_parameters: run.beamline_parameters,
|
||||
experimentType,
|
||||
numberOfImages: numImages,
|
||||
images: sample.images,
|
||||
};
|
||||
treeRows.push(runRow);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Fetch data initially.
|
||||
fetchData();
|
||||
|
||||
// Set up an interval to refresh data every 15 seconds (adjust as needed)
|
||||
const intervalId = setInterval(fetchData, 15000);
|
||||
|
||||
// Clean up when component unmounts or activePgroup changes.
|
||||
return () => clearInterval(intervalId);
|
||||
setRows(treeRows);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching sample results:', error);
|
||||
});
|
||||
}, [activePgroup]);
|
||||
|
||||
|
||||
// Define the grid columns, including the new processing results column.
|
||||
const columns: GridColDef[] = [
|
||||
{ field: 'sample_id', headerName: 'ID', width: 70 },
|
||||
{ field: 'sample_name', headerName: 'Sample Name', width: 150 },
|
||||
{ field: 'puck_name', headerName: 'Puck Name', width: 150 },
|
||||
{ field: 'dewar_name', headerName: 'Dewar Name', width: 150 },
|
||||
{
|
||||
field: 'images',
|
||||
headerName: 'Images',
|
||||
width: 300,
|
||||
field: 'sample_name',
|
||||
headerName: 'Sample Name',
|
||||
width: 200,
|
||||
renderCell: (params) => (params.row.type === 'sample' ? params.value : null),
|
||||
},
|
||||
{
|
||||
field: 'puck_name',
|
||||
headerName: 'Puck Name',
|
||||
width: 150,
|
||||
renderCell: (params) => (params.row.type === 'sample' ? params.value : null),
|
||||
},
|
||||
{
|
||||
field: 'dewar_name',
|
||||
headerName: 'Dewar Name',
|
||||
width: 150,
|
||||
renderCell: (params) => (params.row.type === 'sample' ? params.value : null),
|
||||
},
|
||||
{
|
||||
field: 'experimentType',
|
||||
headerName: 'Experiment Type',
|
||||
width: 150,
|
||||
renderCell: (params) => (params.row.type === 'run' ? params.value : null),
|
||||
},
|
||||
{
|
||||
field: 'numberOfImages',
|
||||
headerName: 'Number of Images',
|
||||
width: 150,
|
||||
renderCell: (params) => (params.row.type === 'run' ? params.value : null),
|
||||
},
|
||||
{
|
||||
field: 'processingResults',
|
||||
headerName: 'Processing Results',
|
||||
width: 180,
|
||||
renderCell: (params) => {
|
||||
const imageList: ImageInfo[] = params.value;
|
||||
if (!imageList || imageList.length === 0) {
|
||||
return null;
|
||||
if (params.row.type === 'run') {
|
||||
return (
|
||||
<IconButton
|
||||
aria-label="processing results placeholder"
|
||||
onClick={() => {
|
||||
// Placeholder for processing results details.
|
||||
console.log('Clicked processing details for run', params.row.run_number);
|
||||
}}
|
||||
>
|
||||
<InfoIcon />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
// Filter the images to include only the two bb_raster images
|
||||
const filteredImages = imageList.filter(
|
||||
(img) =>
|
||||
img.filepath.includes("bb_raster_0") ||
|
||||
img.filepath.includes("bb_raster_90")
|
||||
);
|
||||
|
||||
if (filteredImages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
{filteredImages.map((img) => {
|
||||
const url = basePath + img.filepath;
|
||||
return (
|
||||
<img
|
||||
key={img.id}
|
||||
src={url}
|
||||
alt={img.comment || 'sample'}
|
||||
className="zoom-image"
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
marginRight: 5,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
},
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
return (
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
pageSize={10}
|
||||
getRowId={(row) => row.sample_id}
|
||||
sx={{
|
||||
'& .MuiDataGrid-cell': {
|
||||
overflow: 'visible',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<div style={{ height: 600, width: '100%' }}>
|
||||
<DataGridPremium
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
treeData
|
||||
getTreeDataPath={(row: TreeRow) => row.hierarchy}
|
||||
defaultGroupingExpansionDepth={-1}
|
||||
getRowId={(row) => row.id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
84
frontend/src/components/RunDetails.tsx
Normal file
84
frontend/src/components/RunDetails.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import {SimpleTreeView, TreeItem} from "@mui/x-tree-view";
|
||||
import React from "react";
|
||||
|
||||
|
||||
interface ExperimentParameters {
|
||||
id: number;
|
||||
run_number: number;
|
||||
beamline_parameters: {
|
||||
synchrotron: string;
|
||||
beamline: string;
|
||||
detector: {
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
type: string;
|
||||
serial_number: string;
|
||||
detector_distance_mm: number;
|
||||
beam_center_x_px: number;
|
||||
beam_center_y_px: number;
|
||||
pixel_size_x_um: number;
|
||||
pixel_size_y_um: number;
|
||||
number_of_images: number;
|
||||
exposure_time_s: number;
|
||||
};
|
||||
// Add additional fields if needed.
|
||||
};
|
||||
}
|
||||
|
||||
<SimpleTreeView
|
||||
defaultCollapseIcon="▾"
|
||||
defaultExpandIcon="▸"
|
||||
sx={{ fontSize: '0.875rem' }}
|
||||
>
|
||||
<TreeItem nodeId="detector-group" label={<strong>Detector Details</strong>}>
|
||||
<TreeItem nodeId="detector-group" label={<strong>Detector Details</strong>}>
|
||||
<TreeItem
|
||||
nodeId="detector-manufacturer"
|
||||
label={`Manufacturer: ${detector?.manufacturer || 'N/A'}`}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="detector-model"
|
||||
label={`Model: ${detector?.model || 'N/A'}`}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="detector-type"
|
||||
label={`Type: ${detector?.type || 'N/A'}`}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="detector-serial"
|
||||
label={`Serial Number: ${detector?.serial_number || 'N/A'}`}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="detector-distance"
|
||||
label={`Distance (mm): ${detector?.detector_distance_mm ?? 'N/A'}`}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="beam-center"
|
||||
label={
|
||||
detector
|
||||
? `Beam Center: x:${detector.beam_center_x_px}, y:${detector.beam_center_y_px}`
|
||||
: 'Beam Center: N/A'
|
||||
}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="pixel-size"
|
||||
label={
|
||||
detector
|
||||
? `Pixel Size (µm): x:${detector.pixel_size_x_um}, y:${detector.pixel_size_y_um}`
|
||||
: 'Pixel Size: N/A'
|
||||
}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="img-count"
|
||||
label={`Number of Images: ${detector?.number_of_images ?? 'N/A'}`}
|
||||
/>
|
||||
<TreeItem
|
||||
nodeId="exposure-time"
|
||||
label={`Exposure Time (s): ${detector?.exposure_time_s ?? 'N/A'}`}
|
||||
/>
|
||||
</TreeItem>
|
||||
|
||||
|
||||
|
||||
</TreeItem>
|
||||
</SimpleTreeView>
|
@ -52,7 +52,7 @@ const SampleTracker: React.FC<SampleTrackerProps> = ({ activePgroup }) => {
|
||||
fetchPucks();
|
||||
const interval = setInterval(() => {
|
||||
fetchPucks();
|
||||
}, 1000);
|
||||
}, 100000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [activePgroup]);
|
||||
|
Reference in New Issue
Block a user