From d6d831396747a3fb886e39b3dc62bdcc2864bb0b Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Wed, 14 Oct 2026 17:02:38 +0100 Subject: [PATCH 1/7] Add skeleton process/view tabbed component --- .../WorkflowsDataFetcher/TabbedView.tsx | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx new file mode 100644 index 00000000..d785e370 --- /dev/null +++ b/frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx @@ -0,0 +1,33 @@ +import { Tab, Tabs } from "@mui/material"; +import { useState } from "react"; + +const TAB_TITLES = ["Process", "View"]; + +export const TabbedView = () => { + const [tabIndex, setTabIndex] = useState(0); + + return ( + <> + setTabIndex(newValue)} + > + {TAB_TITLES.map((title) => ( + + ))} + + {TAB_TITLES.map((_, idx) => ( + + ))} + + ); +}; + +type TabContentProps = { + index: number; + selectedIndex: number; +}; + +const TabContent = ({ index, selectedIndex }: TabContentProps) => { + return ; +}; From b73c47a15b5996d95137442f27e1795d823d22c4 Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Wed, 14 Oct 2026 17:07:22 +0100 Subject: [PATCH 2/7] Move plot and log components into tabbed view --- .../WorkflowsDataFetcher/TabbedView.tsx | 33 -------- .../WorkflowDataViewer.tsx | 76 +++++++++---------- 2 files changed, 36 insertions(+), 73 deletions(-) delete mode 100644 frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx deleted file mode 100644 index d785e370..00000000 --- a/frontend/unified/src/components/WorkflowsDataFetcher/TabbedView.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Tab, Tabs } from "@mui/material"; -import { useState } from "react"; - -const TAB_TITLES = ["Process", "View"]; - -export const TabbedView = () => { - const [tabIndex, setTabIndex] = useState(0); - - return ( - <> - setTabIndex(newValue)} - > - {TAB_TITLES.map((title) => ( - - ))} - - {TAB_TITLES.map((_, idx) => ( - - ))} - - ); -}; - -type TabContentProps = { - index: number; - selectedIndex: number; -}; - -const TabContent = ({ index, selectedIndex }: TabContentProps) => { - return ; -}; diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx index 6921bb69..ce95a6ab 100644 --- a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx +++ b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx @@ -1,15 +1,10 @@ -import { - Accordion, - AccordionDetails, - AccordionSummary, - Typography, -} from "@mui/material"; +import { useState } from "react"; +import { Tabs, Tab } from "@mui/material"; import { Visit } from "@diamondlightsource/sci-react-ui"; import LogStreamer from "./LogStreamer/LogStreamer"; import { Plot } from "./Plot/Plot"; import { useSuspenseQuery } from "@apollo/client/react"; import InspectLogMeta from "./InspectLogMeta"; -import { ChevronDown } from "lucide-react"; import { WORKFLOWDATAVIEWER_QUERY } from "../../graphql/WorkflowDataFetcher/WorkflowDataViewerQuery"; const WorkflowDataViewer = ({ @@ -19,6 +14,7 @@ const WorkflowDataViewer = ({ visit: Visit; selectedWorkflow: string; }) => { + const [tabIndex, setTabIndex] = useState(0); const { data, error } = useSuspenseQuery(WORKFLOWDATAVIEWER_QUERY, { variables: { visit: visit, @@ -32,42 +28,42 @@ const WorkflowDataViewer = ({ return ( <> - - }> - Log - - - - - + setTabIndex(newValue)} + > + + + + + + + - - }> - Log Stream - - - - - - - - }> - Plot - - - - - + + + ); }; +type TabContentProps = { + index: number; + selectedIndex: number; + children: React.ReactNode; +}; + +const TabContent = ({ children, index, selectedIndex }: TabContentProps) => { + return ; +}; + export default WorkflowDataViewer; From e8791b62570d03f802fca308841847d3e560187d Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Wed, 14 Oct 2026 17:16:06 +0100 Subject: [PATCH 3/7] Move process view component into tabbed view --- frontend/unified/src/App.tsx | 20 +++++----------- .../WorkflowDataViewer.tsx | 23 +++++++++++++++++-- .../WorkflowsDataFetcher.tsx | 18 ++++++++++++++- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/frontend/unified/src/App.tsx b/frontend/unified/src/App.tsx index 1650a7d7..cbdbdb6d 100644 --- a/frontend/unified/src/App.tsx +++ b/frontend/unified/src/App.tsx @@ -187,20 +187,12 @@ export const App: React.FC = () => { - - - - - - - - + ); diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx index ce95a6ab..05ae6ca9 100644 --- a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx +++ b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx @@ -6,13 +6,22 @@ import { Plot } from "./Plot/Plot"; import { useSuspenseQuery } from "@apollo/client/react"; import InspectLogMeta from "./InspectLogMeta"; import { WORKFLOWDATAVIEWER_QUERY } from "../../graphql/WorkflowDataFetcher/WorkflowDataViewerQuery"; +import { Beamline } from "../../types"; +import { InstrumentSession } from "../SessionSelector"; +import ProcessingView from "../ProcessingView/ProcessingView"; const WorkflowDataViewer = ({ visit, selectedWorkflow, + beamline, + startTime, + scanIds, }: { visit: Visit; selectedWorkflow: string; + beamline: Beamline; + startTime: InstrumentSession["startTime"]; + scanIds: number[]; }) => { const [tabIndex, setTabIndex] = useState(0); const { data, error } = useSuspenseQuery(WORKFLOWDATAVIEWER_QUERY, { @@ -33,10 +42,11 @@ const WorkflowDataViewer = ({ value={tabIndex} onChange={(_, newValue: number) => setTabIndex(newValue)} > + - + - + + + + + ); }; diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx index 7f5a3e73..b3e6c2ee 100644 --- a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx +++ b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx @@ -10,8 +10,21 @@ import { } from "@mui/material"; import { ChevronDown } from "lucide-react"; import WorkflowDataViewer from "./WorkflowDataViewer"; +import { Beamline } from "../../types"; +import { InstrumentSession } from "../SessionSelector"; -const WorkflowsDataFetcher = ({ visit }: { visit: Visit }) => { +const WorkflowsDataFetcher = ({ + visit, + beamline, + startTime, + scanIds, +}: { + visit: Visit; + + beamline: Beamline; + startTime: InstrumentSession["startTime"]; + scanIds: number[]; +}) => { const [selectedWorkflow, setSelectedWorkflow] = useState(null); return ( @@ -34,6 +47,9 @@ const WorkflowsDataFetcher = ({ visit }: { visit: Visit }) => { )} From cc91064ca197730c3261edf53068ee3b4682eadb Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Wed, 14 Oct 2026 17:30:03 +0100 Subject: [PATCH 4/7] Remove unnecessary stack This reduces the vertical movement of components when switching between tabs in the tabbed view component. --- .../components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx index b3e6c2ee..264c13f9 100644 --- a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx +++ b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowsDataFetcher.tsx @@ -5,7 +5,6 @@ import { Accordion, AccordionDetails, AccordionSummary, - Stack, Typography, } from "@mui/material"; import { ChevronDown } from "lucide-react"; @@ -27,7 +26,7 @@ const WorkflowsDataFetcher = ({ }) => { const [selectedWorkflow, setSelectedWorkflow] = useState(null); return ( - + <> }> @@ -53,7 +52,7 @@ const WorkflowsDataFetcher = ({ /> )} - + ); }; From 084dc85074cfc8e4348f6a6981eb09641eed8193 Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Thu, 15 Oct 2026 08:58:19 +0100 Subject: [PATCH 5/7] Wrap text inputs in stack This prevents the loader text inputs from taking up unnecessary horizontal space in the unified page, while also keeping the appearance in the tomo app the same. --- .../src/components/loader/Loader.tsx | 439 +++++++++--------- 1 file changed, 221 insertions(+), 218 deletions(-) diff --git a/frontend/tomography/src/components/loader/Loader.tsx b/frontend/tomography/src/components/loader/Loader.tsx index 0ba90610..e70f73b6 100644 --- a/frontend/tomography/src/components/loader/Loader.tsx +++ b/frontend/tomography/src/components/loader/Loader.tsx @@ -11,6 +11,7 @@ import { Modal, Tooltip, InputAdornment, + Stack, } from "@mui/material"; import ContrastIcon from "@mui/icons-material/Contrast"; import PreviewIcon from "@mui/icons-material/Visibility"; @@ -185,55 +186,25 @@ const Loader: React.FC = () => { - { - if (!autoToggles.dataPath) setDataPath(e.target.value); - }} - placeholder="path to the dataset in the input hdf5/NeXuS file containing image data" - sx={{ mb: 1 }} - InputProps={{ - endAdornment: ( - - - - ), - }} - /> - - {!showExtraFields && ( + { - if (!autoToggles.imagePath) setImageKeyPath(e.target.value); + if (!autoToggles.dataPath) setDataPath(e.target.value); }} - placeholder="path to the image key in your data" + placeholder="path to the dataset in the input hdf5/NeXuS file containing image data" sx={{ mb: 1 }} InputProps={{ endAdornment: ( + + ), + }} + /> + )} - {/* Input Fields */} - {mode === "setAddress" ? ( - { - if (!autoToggles.rotationAnglePath) - setRotationAnglesDataPath(e.target.value); - }} - placeholder="Path to rotation angle dataset in your data" + color="primary" + onChange={handleModeChange} + aria-label="Rotation Angle Mode" + fullWidth sx={{ mb: 1 }} - InputProps={{ - endAdornment: ( - - - - ), - }} - /> - ) : ( - - - - setUserDefinedRotationAngles( - Number(e.target.value), - parameters.rotation_angles.user_defined?.stop_angle || 0, - parameters.rotation_angles.user_defined?.angles_total || 0 - ) - } - placeholder="First angle" - /> - - - - setUserDefinedRotationAngles( - parameters.rotation_angles.user_defined?.start_angle || 0, - Number(e.target.value), - parameters.rotation_angles.user_defined?.angles_total || 0 - ) - } - placeholder="Last angle" - /> - - - - setUserDefinedRotationAngles( - parameters.rotation_angles.user_defined?.start_angle || 0, - parameters.rotation_angles.user_defined?.stop_angle || 0, - Number(e.target.value) - ) - } - placeholder="Total angles between" - /> - - - )} + > + + use rotation angle within data + + + set custom rotation angle + + - {showExtraFields && ( - - - - Darks - - - setDarks(e.target.value, parameters.darks?.data_path || "") - } - sx={{ mb: 1 }} - placeholder="path to the hdf5/NeXuS darks file " - /> - - setDarks(parameters.darks?.file || "", e.target.value) - } - sx={{ mb: 1 }} - placeholder="dataset that contains the darks data" - /> + {/* Input Fields */} + {mode === "setAddress" ? ( + { + if (!autoToggles.rotationAnglePath) + setRotationAnglesDataPath(e.target.value); + }} + placeholder="Path to rotation angle dataset in your data" + sx={{ mb: 1 }} + InputProps={{ + endAdornment: ( + + + + ), + }} + /> + ) : ( + + + + setUserDefinedRotationAngles( + Number(e.target.value), + parameters.rotation_angles.user_defined?.stop_angle || 0, + parameters.rotation_angles.user_defined?.angles_total || 0 + ) + } + placeholder="First angle" + /> + + + + setUserDefinedRotationAngles( + parameters.rotation_angles.user_defined?.start_angle || 0, + Number(e.target.value), + parameters.rotation_angles.user_defined?.angles_total || 0 + ) + } + placeholder="Last angle" + /> + + + + setUserDefinedRotationAngles( + parameters.rotation_angles.user_defined?.start_angle || 0, + parameters.rotation_angles.user_defined?.stop_angle || 0, + Number(e.target.value) + ) + } + placeholder="Total angles between" + /> + - - - Flats - - - setFlats(e.target.value, parameters.flats?.data_path || "") - } - sx={{ mb: 1 }} - placeholder="path to the hdf5/NeXuS flats file" - /> - - setFlats(parameters.flats?.file || "", e.target.value) - } - sx={{ mb: 1 }} - placeholder="dataset that contains the flats data" - /> + )} + + {showExtraFields && ( + + + + Darks + + + setDarks(e.target.value, parameters.darks?.data_path || "") + } + sx={{ mb: 1 }} + placeholder="path to the hdf5/NeXuS darks file " + /> + + setDarks(parameters.darks?.file || "", e.target.value) + } + sx={{ mb: 1 }} + placeholder="dataset that contains the darks data" + /> + + + + Flats + + + setFlats(e.target.value, parameters.flats?.data_path || "") + } + sx={{ mb: 1 }} + placeholder="path to the hdf5/NeXuS flats file" + /> + + setFlats(parameters.flats?.file || "", e.target.value) + } + sx={{ mb: 1 }} + placeholder="dataset that contains the flats data" + /> + - - )} + )} + Date: Thu, 15 Oct 2026 10:21:52 +0100 Subject: [PATCH 6/7] Display process tab even if no workflow is selected --- .../WorkflowDataViewer.tsx | 65 ++++++++++++------- .../WorkflowsDataFetcher.tsx | 20 +++--- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx index 05ae6ca9..99943f57 100644 --- a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx +++ b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx @@ -10,6 +10,8 @@ import { Beamline } from "../../types"; import { InstrumentSession } from "../SessionSelector"; import ProcessingView from "../ProcessingView/ProcessingView"; +const EMPTY_WORKFLOW_NAME_QUERY_ERROR = "resource name may not be empty"; + const WorkflowDataViewer = ({ visit, selectedWorkflow, @@ -18,7 +20,7 @@ const WorkflowDataViewer = ({ scanIds, }: { visit: Visit; - selectedWorkflow: string; + selectedWorkflow: string | null; beamline: Beamline; startTime: InstrumentSession["startTime"]; scanIds: number[]; @@ -27,13 +29,14 @@ const WorkflowDataViewer = ({ const { data, error } = useSuspenseQuery(WORKFLOWDATAVIEWER_QUERY, { variables: { visit: visit, - name: selectedWorkflow, + name: selectedWorkflow ?? "", }, + errorPolicy: "all", }); - if (error) return <>Error: {error.message}; - if (!data) return <>No data; - if (!data.workflow) return <>No workflow data; + if (error && !error.message.includes(EMPTY_WORKFLOW_NAME_QUERY_ERROR)) + return <>Error: {error.message}; + if (!error && !data) return <>No data; return ( <> @@ -43,25 +46,43 @@ const WorkflowDataViewer = ({ onChange={(_, newValue: number) => setTabIndex(newValue)} > - - - - - - - - - - - + + {selectedWorkflow && data && data.workflow && ( + <> + + + + + + + + + + )} - {selectedWorkflow && ( - - - - )} + + + ); }; From ddaadcd532dddc6a508d781a5735d8b36604c396 Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Thu, 15 Oct 2026 10:29:11 +0100 Subject: [PATCH 7/7] Change tab to process view if workflow unselected --- .../WorkflowsDataFetcher/WorkflowDataViewer.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx index 99943f57..00fadd1a 100644 --- a/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx +++ b/frontend/unified/src/components/WorkflowsDataFetcher/WorkflowDataViewer.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Tabs, Tab } from "@mui/material"; import { Visit } from "@diamondlightsource/sci-react-ui"; import LogStreamer from "./LogStreamer/LogStreamer"; @@ -34,6 +34,12 @@ const WorkflowDataViewer = ({ errorPolicy: "all", }); + useEffect(() => { + if (!selectedWorkflow) { + setTabIndex(0); + } + }, [selectedWorkflow]); + if (error && !error.message.includes(EMPTY_WORKFLOW_NAME_QUERY_ERROR)) return <>Error: {error.message}; if (!error && !data) return <>No data;