diff --git a/frontend/src/Layout.tsx b/frontend/src/Layout.tsx index 26392f49..4910684a 100644 --- a/frontend/src/Layout.tsx +++ b/frontend/src/Layout.tsx @@ -66,7 +66,7 @@ const Layout = () => { = ({ session }: AppProps) => { - const [selectedScanIds, setSelectedScanIds] = useState([]); + const [selectedScanIds, setSelectedScanIds] = useState( + [] + ); + const [showParamConfigDrawer, setShowParamConfigDrawer] = useState(false); const [workflowsViewMode, setWorkflowsViewMode] = useState( @@ -48,7 +56,7 @@ export const App: React.FC = ({ session }: AppProps) => { }, [sessionRef]); return ( - + = ({ session }: AppProps) => { @@ -84,17 +92,18 @@ export const App: React.FC = ({ session }: AppProps) => { visit={selectedVisit} beamline={beamline} startTime={session.startTime} - scanIds={selectedScanIds} + scanIds={selectedScanIds.map((x) => x.scanNumber)} /> - + diff --git a/frontend/unified/src/beamline.config.ts b/frontend/unified/src/beamline.config.ts index a6c8f774..9398ecc0 100644 --- a/frontend/unified/src/beamline.config.ts +++ b/frontend/unified/src/beamline.config.ts @@ -138,9 +138,25 @@ const BEAMLINE_CONFIG: BeamlineConfig[] = [ scanRequest: (v) => v.data?.scan_parameters?.scan_request, components: [ { - title: "Sample name", - getter: (v) => v.data?.sample_information?.name, - formatter: FORMATTER.NONE, + groupTitle: "Experiment", + children: [ + { + title: "Sample name", + getter: (v) => v.data?.sample_information?.name, + formatter: FORMATTER.NONE, + }, + { + title: "Experiment type", + getter: (v) => { + const req = v?.scanRequest?.processingRequest?.request; + if (req) { + return Object.keys(req); + } + return null; + }, + formatter: (v) => v.join(", "), + }, + ], }, { groupTitle: "Times", diff --git a/frontend/unified/src/components/MetadataDisplay.tsx b/frontend/unified/src/components/MetadataDisplay.tsx new file mode 100644 index 00000000..db838ccf --- /dev/null +++ b/frontend/unified/src/components/MetadataDisplay.tsx @@ -0,0 +1,117 @@ +import { Typography, Stack, Skeleton, Grid } from "@mui/material"; +import type { ScanNodeProcessed } from "../types"; +import { getBeamlineConfig, isMetadataComponent } from "../beamline.config"; + +type MetadataDisplayProp = { + selectedScanData: ScanNodeProcessed[]; + condensed?: boolean; +}; +export function MetadataDisplay({ + selectedScanData, + condensed, +}: MetadataDisplayProp) { + const firstScan = selectedScanData.at(0); + + if (!firstScan) { + return No scans selected; + } + + const schema = firstScan.dataSchema; + + const config = getBeamlineConfig(schema); + const title = makeTitle(selectedScanData); + const multipleSelected = selectedScanData.length > 1; + + let maxColumns = 1; + let components = config?.components; + if (condensed) { + // If condensed, limit the data display to the first two groups, + // and place each group into its own row + const columns = config?.components.map((x) => { + if (!isMetadataComponent(x)) { + return x.children.length; + } else { + return 1; + } + }); + maxColumns = columns ? Math.max(...columns) : 1; + components = components?.slice(0, 2); + } + + return ( + + {!title ? : title} + + {components && + firstScan && + !multipleSelected && + components.map((x) => { + if (isMetadataComponent(x)) { + const val = x.getter(firstScan); + return ( + + + + ); + } else { + return ( + // Draw containers + + {x.children.map((y) => { + const val = y.getter(firstScan); + return ( + + + + ); + })} + + ); + } + })} + + + ); +} + +type SingleMetadataProp = { + label: string; + value: string; +}; +export function SingleMetadata({ label, value }: SingleMetadataProp) { + return ( + + + {label} + + + {value} + + + ); +} + +export function makeTitle(scanData: ScanNodeProcessed[]): string | null { + if (scanData.length === 0) { + return null; + } else if (scanData.length === 1) { + const scanNumber = scanData.at(0)?.scanNumber; + if (scanNumber) { + return `${scanNumber}`; + } else { + return null; + } + } else { + return `Selected ${scanData.length} files`; + } +} diff --git a/frontend/unified/src/components/ScanSelector.tsx b/frontend/unified/src/components/ScanSelector.tsx index 6a44729e..4b8095f3 100644 --- a/frontend/unified/src/components/ScanSelector.tsx +++ b/frontend/unified/src/components/ScanSelector.tsx @@ -44,9 +44,9 @@ const columns = [ type ScanSelectorProp = { sessionRef: string; - onScanSelect: (scans: ScanNodeProcessed[]) => void; + updateScanData: (scans: ScanNodeProcessed[]) => void; }; -export function ScanSelector({ sessionRef, onScanSelect }: ScanSelectorProp) { +export function ScanSelector({ sessionRef, updateScanData }: ScanSelectorProp) { const tableContainerRef = useRef(null); //we can get access to the underlying TableContainer element and react to its scroll events const FETCH_SIZE = 30; @@ -218,7 +218,7 @@ export function ScanSelector({ sessionRef, onScanSelect }: ScanSelectorProp) { const selectedData = selectedIdx .filter((x) => rowSelection[x]) .map((x) => table.getRow(x).original); - onScanSelect(selectedData); + updateScanData(selectedData); // `onScanSelect` should not be placed into dependency list as it causes an infinite loop }, [table.getState().rowSelection]); @@ -244,5 +244,10 @@ export function ScanSelector({ sessionRef, onScanSelect }: ScanSelectorProp) { prevDesc.current = isDescSort(sorting); }, [sorting, client, fetchMore, sessionRef]); + // Clear row selection when session ref changes + useEffect(() => { + setRowSelection({}); + }, [sessionRef]); + return ; } diff --git a/frontend/unified/src/components/ScanSelectorCombined.tsx b/frontend/unified/src/components/ScanSelectorCombined.tsx index 2ea49883..f9167163 100644 --- a/frontend/unified/src/components/ScanSelectorCombined.tsx +++ b/frontend/unified/src/components/ScanSelectorCombined.tsx @@ -2,16 +2,17 @@ import { Stack, ToggleButtonGroup, ToggleButton } from "@mui/material"; import { useState } from "react"; import { ScanSelector } from "./ScanSelector"; import { ScanSelector as ScanSelectorLegacy } from "./ScanSelectorLegacy"; +import { ScanNodeProcessed } from "../types"; type ScanSelectorCombinedProps = { sessionRef: string; - scanIds: number[]; - setScanIds: (v: number[]) => void; + scanData: ScanNodeProcessed[]; + updateScanData: (v: ScanNodeProcessed[]) => void; }; export const ScanSelectorCombined = ({ sessionRef, - scanIds, - setScanIds, + scanData, + updateScanData, }: ScanSelectorCombinedProps) => { const [selectorType, setSelectorType] = useState("legacy"); @@ -35,13 +36,33 @@ export const ScanSelectorCombined = ({ {selectorType === "new" ? ( - setScanIds(v.map((x) => x.scanNumber))} - sessionRef={sessionRef} - /> + ) : ( - + x.scanNumber)} + setScanIds={(v) => updateScanData(generateScanData(v))} + /> )} ); }; + +// Generates dummy detailed scan data when using the legacy +// scan selector +const generateScanData = (scanIds: number[]): ScanNodeProcessed[] => { + return scanIds.map((x) => { + return { + __typename: "Dataset", + scanNumber: x, + dataSchema: "", + description: null, + id: -1, + title: "Title", + beamline: null, + startTime: null, + endTime: null, + scanRequest: null, + data: {}, + }; + }); +}; diff --git a/frontend/unified/src/components/Sidebar.tsx b/frontend/unified/src/components/Sidebar.tsx index 817b3e63..b6339e8a 100644 --- a/frontend/unified/src/components/Sidebar.tsx +++ b/frontend/unified/src/components/Sidebar.tsx @@ -1,16 +1,17 @@ import { Navbar } from "@diamondlightsource/sci-react-ui"; import { Box, Typography, Stack } from "@mui/material"; import { ScanSelectorCombined } from "./ScanSelectorCombined"; +import { ScanNodeProcessed } from "../types"; type SidebarProps = { - scanIds: number[]; - updateSelectedScanIds: (_: number[]) => void; + scanData: ScanNodeProcessed[]; + updateScanData: (_: ScanNodeProcessed[]) => void; sessionRef: string; }; export const Sidebar: React.FC = ({ - scanIds, - updateSelectedScanIds, + scanData, + updateScanData, sessionRef, }: SidebarProps) => { return ( @@ -37,8 +38,8 @@ export const Sidebar: React.FC = ({ This is to ensure infinite scroll works correctly. */} diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx index 322c4de9..e1f8205a 100644 --- a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx +++ b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx @@ -5,20 +5,24 @@ import { Accordion, AccordionDetails, AccordionSummary, + Box, Stack, Typography, } from "@mui/material"; import { ChevronDown } from "lucide-react"; import WorkflowDataViewer from "./WorkflowDataViewer"; import { WorkflowsList } from "./WorkflowsList/WorkflowsList"; -import { WorkflowsViewMode } from "../../types"; +import { ScanNodeProcessed, WorkflowsViewMode } from "../../types"; +import { MetadataDisplay } from "../MetadataDisplay"; const WorkflowsDataFetcher = ({ visit, workflowsViewMode, + scanData, }: { visit: Visit; workflowsViewMode: WorkflowsViewMode; + scanData: ScanNodeProcessed[]; }) => { const [selectedWorkflow, setSelectedWorkflow] = useState(null); @@ -26,33 +30,44 @@ const WorkflowsDataFetcher = ({ <> {workflowsViewMode === WorkflowsViewMode.Table ? ( - - - }> - Jobs - - - + + + + + + + }> + Jobs + + + + + + {selectedWorkflow && ( + - - - {selectedWorkflow && ( - - )} + )} + ) : ( - + + + + + + {selectedWorkflow && (