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
51 changes: 51 additions & 0 deletions frontend/i14/src/components/workflows/OptionSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { FC } from "react";
import {
FormControl,
InputLabel,
Select,
IconButton,
Tooltip,
Grid,
} from "@mui/material";
import InfoIcon from "@mui/icons-material/Info";
import type { SelectChangeEvent } from "@mui/material/Select";
import type { Option } from "../../types/workflowFields";

type Props = {
label: string;
value: string;
options: Option[];
onChange: (e: SelectChangeEvent<string>) => void;
};

const OptionSelect: FC<Props> = ({ label, value, options, onChange }) => {
const selected = options.find((o) => o.value === value);

return (
<FormControl>
<InputLabel shrink>{label}</InputLabel>
<Grid>
<Select
native
label={label}
size="small"
value={value}
onChange={onChange}
>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</Select>
<Tooltip title={selected?.desc ?? ""}>
<IconButton>
<InfoIcon />
</IconButton>
</Tooltip>
</Grid>
</FormControl>
);
};

export default OptionSelect;
220 changes: 108 additions & 112 deletions frontend/i14/src/components/workflows/WorkflowForm.tsx
Original file line number Diff line number Diff line change
@@ -1,139 +1,135 @@
import React, { FC, ChangeEvent, useState } from "react";
import React, { FC, useMemo, useState } from "react";
import {
Button,
MenuItem,
IconButton,
InputLabel,
FormLabel,
TextField,
ToggleButton,
ToggleButtonGroup,
Grid,
Select,
Stack,
TextField,
Tooltip,
Typography,
} from "@mui/material";
import InfoIcon from "@mui/icons-material/Info";
import type { SelectChangeEvent } from "@mui/material/Select";
import { visitRegex } from "@diamondlightsource/sci-react-ui";

type FormData = { visit: string; workflow: string };
import { initialData } from "../../data/form";
import { templateOptions } from "../../data/templates";
import OptionSelect from "./OptionSelect";

const initialData: FormData = {
visit: "cm23467-2",
workflow: "dpc-batch",
};
import type { WorkflowFormData, Option } from "../../types/workflowFields";

export const WorkflowForm: FC = () => {
const [data, setData] = useState<FormData>(initialData);
const techniques = ["dpc", "xanes", "xrd"] as const;
type ToggleGroup = (typeof techniques)[number];
const getFilteredTemplates = (toggle: ToggleGroup): Option[] => {
return (templateOptions ?? []).filter((o) => o.value.includes(toggle));
};

const [data, setData] = useState<WorkflowFormData>(() => {
const defaultTechnique =
techniques.find((t) => initialData.template.includes(t)) ?? techniques[0];
return {
...initialData,
technique: defaultTechnique,
};
});

const visitMatch = visitRegex.exec(data.visit);

const filteredTemplateOptions: Option[] = getFilteredTemplates(
data.technique
);

const handleToggleChange = (
_event: React.MouseEvent<HTMLElement>,
next: ToggleGroup | null
) => {
if (!next) return;
const filteredTemplates = getFilteredTemplates(next);
setData((prev) => ({
...prev,
technique: next,
template: filteredTemplates[0].value,
}));
};

const openInNewTab = (url: string) => {
const w = window.open(url, "_blank");
if (w) w.focus();
w?.focus();
};

const openLink = () => {
if (!visitMatch) return;
const linkString = `https://workflows.diamond.ac.uk/templates/${data.workflow}/${data.visit}`;
openInNewTab(linkString);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const url = `https://workflows.diamond.ac.uk/templates/${data.template}/${data.visit}?outputFolder=${data.outpath}`;
openInNewTab(url);
};

const workflowOptions = [
{
label: "DPC",
value: "dpc-batch",
desc: "DPC imaging produces an image of the phase shifts to the X-ray beam as a result of interaction with the sample by measuring the gradient of the phase and retrieving the phase shifts by a straightforward integration step.",
},
{
label: "XANES Auto-processing",
value: "xanes",
desc: "XANES is a utility which attempts to stack a sequence of datasets acquired at different energy for a particular line group and perform alignment based on a line group.",
},
{
label: "XANES Point",
value: "xanes-point",
desc: "XANES-point is a utility which takes in a scan file (inpath) and a line group (edge_element), performs windowing of this line group, and saves a two-column text file (outpath): the first column is the energy in keV, and the second column is the summed windowed MCA intensity across the 4 channels.",
},
{
label: "XANES Sparse",
value: "xanes-sparse",
desc: "XANES-sparse is a utility which takes the last scan file of a sparse XANES scan (inpath), defines the 2D full grid, inserts the data in the correct rows, stack the images, and completes the missing data by using looped alternating steepest descent (ASD).",
},
{
label: "XRD 1D",
value: "xrd1d-batch",
desc: "XRD can be used to spatially map changes in crystallographic direction, d-spacing or strain across a sample.",
},
{
label: "XRD 2D",
value: "xrd2d-batch",
desc: "XRD 2D is a utility which performs Azimuthal integration (ExcaliburXRDIntegration) and saves result to a nxs file",
},
];

const option = workflowOptions.find(
(option) => option.value === data.workflow
);

return (
<Stack>
<Typography variant="h4">I14 Workflows</Typography>
<Grid container justifyContent="center" spacing={1}>
<Grid item s={6}>
<Typography variant="h4">I14 Workflows</Typography>
<br />
<form onSubmit={handleSubmit}>
<Stack direction="column" spacing={2}>
<Stack direction="column" spacing={0}>
<FormLabel>Technique</FormLabel>
<ToggleButtonGroup
exclusive
value={data.technique}
onChange={handleToggleChange}
aria-label="Technique"
>
{techniques.map((t) => (
<ToggleButton key={t} value={t}>
{t.toUpperCase()}
</ToggleButton>
))}
</ToggleButtonGroup>
</Stack>
<OptionSelect
label="Template"
value={data.template}
options={filteredTemplateOptions}
onChange={(e) =>
setData((prev) => ({ ...prev, template: e.target.value }))
}
/>

<form
onSubmit={(e) => {
e.preventDefault();
openLink();
}}
>
<Stack direction="column" spacing={2}>
<InputLabel size="small" id="workflow-select-label">
Workflow
</InputLabel>
<Grid>
<Select
labelId="workflow-select-label"
label="Workflow"
<TextField
name="visit"
label="Visit"
variant="outlined"
size="small"
name="workflow"
value={data.workflow}
onChange={(e: SelectChangeEvent<string>) =>
setData((prev) => ({ ...prev, workflow: e.target.value }))
}
>
{workflowOptions.map((opt) => (
<MenuItem key={opt.value} value={opt.value}>
{opt.label}
</MenuItem>
))}
</Select>
<Tooltip title={option.desc}>
<IconButton>
<InfoIcon />
</IconButton>
</Tooltip>
</Grid>
<TextField
name="visit"
label="Visit"
variant="outlined"
size="small"
placeholder="Visit"
type="text"
value={data.visit}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setData((prev) => ({ ...prev, visit: value }));
}}
helperText={visitMatch ? "" : "Expected format: xx12345-1"}
error={!visitMatch}
/>
placeholder="Visit"
type="text"
value={data.visit}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setData((prev) => ({ ...prev, visit: value }));
}}
helperText={visitMatch ? "" : "Expected format: xx12345-1"}
error={!visitMatch}
/>

<TextField
name="outpath"
label="Output path"
variant="outlined"
size="small"
placeholder="Output path"
type="text"
value={data.outpath}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setData((prev) => ({ ...prev, outpath: e.target.value }));
}}
/>

<Button variant="contained" type="submit" disabled={!visitMatch}>
Submit
</Button>
</Stack>
</form>
</Stack>
<Button variant="contained" type="submit" disabled={!visitMatch}>
Open workflow form in a new tab
</Button>
</Stack>
</form>
</Grid>
</Grid>
);
};

Expand Down
7 changes: 7 additions & 0 deletions frontend/i14/src/data/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { WorkflowFormData } from "../types/workflowFields";

export const initialData: WorkflowFormData = {
visit: "mg23967-1",
template: "dpc-batch",
outpath: "/dls/i14/data/",
};
34 changes: 34 additions & 0 deletions frontend/i14/src/data/templates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Option } from "../types/workflowFields";

export const templateOptions: Option[] = [
{
label: "DPC",
value: "dpc-batch",
desc: "DPC imaging produces an image of the phase shifts to the X-ray beam as a result of interaction with the sample by measuring the gradient of the phase and retrieving the phase shifts by a straightforward integration step.",
},
{
label: "XANES Auto-processing",
value: "xanes",
desc: "XANES is a utility which attempts to stack a sequence of datasets acquired at different energy for a particular line group and perform alignment based on a line group.",
},
{
label: "XANES Point",
value: "xanes-point",
desc: "XANES-point is a utility which takes in a scan file (inpath) and a line group (edge_element), performs windowing of this line group, and saves a two-column text file (outpath): the first column is the energy in keV, and the second column is the summed windowed MCA intensity across the 4 channels.",
},
{
label: "XANES Sparse",
value: "xanes-sparse",
desc: "XANES-sparse is a utility which takes the last scan file of a sparse XANES scan (inpath), defines the 2D full grid, inserts the data in the correct rows, stack the images, and completes the missing data by using looped alternating steepest descent (ASD).",
},
{
label: "XRD 1D",
value: "xrd1d-batch",
desc: "XRD can be used to spatially map changes in crystallographic direction, d-spacing or strain across a sample.",
},
{
label: "XRD 2D",
value: "xrd2d-batch",
desc: "XRD 2D is a utility which performs Azimuthal integration (ExcaliburXRDIntegration) and saves result to a nxs file",
},
];
2 changes: 2 additions & 0 deletions frontend/i14/src/types/workflowFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type WorkflowFormData = { visit: string; template: string; outpath: string };
type Option = { label: string; value: string; desc?: string };
Loading