Skip to content

Commit bcb2da9

Browse files
committed
Use "stepper" components in model simulation
Make models a step-by-step "guided" form.
1 parent 1550e05 commit bcb2da9

7 files changed

Lines changed: 322 additions & 144 deletions

File tree

Lines changed: 107 additions & 0 deletions
Loading

frontend/src/components/ModelDescription.tsx

Lines changed: 0 additions & 24 deletions
This file was deleted.

frontend/src/components/ModelInputs.tsx

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Autocomplete, Button, NativeSelect, TextField, Typography } from "@equi
44
import ConvertibleTextField from "./ConvertibleTextField.tsx";
55
import { FORMULA_TO_NAME_MAPPER } from "@/constants/formula_map.tsx";
66
import { MetaTooltip } from "@/functions/Tooltip.tsx";
7+
import { Columns } from "@/components/styles.ts";
78

89
const DEFAULTS = {
910
O2: 30,
@@ -62,8 +63,8 @@ function ParametersInput({
6263
if (!model.parameters) return;
6364

6465
return (
65-
<div style={{ display: "flex", flexFlow: "column", gap: "1.5rem", paddingTop: "1em" }}>
66-
<Typography bold> Input parameters </Typography>
66+
<div style={{ display: "flex", flexDirection: "column", gap: "16px", flexGrow: 1 }}>
67+
<Typography variant="h3"> Parameters </Typography>
6768
{Object.entries(model.parameters).map(([name, config]) =>
6869
config.choices ? (
6970
<NativeSelect
@@ -120,36 +121,43 @@ const ModelInputs: React.FC<{
120121
const invisible = model.validSubstances.filter((subst) => !visible.includes(subst));
121122

122123
return (
123-
<>
124-
<Typography bold>Input concentrations</Typography>
125-
{visible.map((subst, index) => (
126-
<TextField
127-
type="number"
128-
key={index}
129-
id={subst}
130-
label={optionName(subst)}
131-
style={{ paddingTop: "5px" }}
132-
step="any"
133-
unit="ppm"
134-
max={PPM_MAX}
135-
value={concentrations[subst] ?? 0}
136-
onChange={(e: ChangeEvent<HTMLInputElement>) =>
137-
setConcentrations({ ...concentrations, [subst]: Math.min(e.target.valueAsNumber, PPM_MAX) })
138-
}
124+
<div>
125+
<Columns>
126+
<div>
127+
<Typography variant="h3">Concentrations</Typography>
128+
{visible.map((subst, index) => (
129+
<TextField
130+
type="number"
131+
key={index}
132+
id={subst}
133+
label={optionName(subst)}
134+
style={{ paddingTop: "5px" }}
135+
step="any"
136+
unit="ppm"
137+
max={PPM_MAX}
138+
value={concentrations[subst] ?? 0}
139+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
140+
setConcentrations({
141+
...concentrations,
142+
[subst]: Math.min(e.target.valueAsNumber, PPM_MAX),
143+
})
144+
}
145+
/>
146+
))}
147+
<SubstanceAdder invisible={invisible} onAdd={(item: string) => setVisible([...visible, item])} />
148+
</div>
149+
<ParametersInput
150+
model={model}
151+
parameters={parameters}
152+
setParameter={(name: string, value: any) => {
153+
setParameters({ ...parameters, [name]: value });
154+
}}
139155
/>
140-
))}
141-
<SubstanceAdder invisible={invisible} onAdd={(item: string) => setVisible([...visible, item])} />
142-
<ParametersInput
143-
model={model}
144-
parameters={parameters}
145-
setParameter={(name: string, value: any) => {
146-
setParameters({ ...parameters, [name]: value });
147-
}}
148-
/>
156+
</Columns>
149157
<Button style={{ marginTop: "1em" }} onClick={() => onSubmit(concentrations, parameters)}>
150158
Run Simulation
151159
</Button>
152-
</>
160+
</div>
153161
);
154162
};
155163

frontend/src/components/ModelSelect.tsx

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,42 @@
1-
import React from "react";
2-
import { Radio, Typography } from "@equinor/eds-core-react";
1+
import React, { useState } from "react";
2+
import { Button, Dialog, Icon, Typography } from "@equinor/eds-core-react";
33
import { ModelConfig } from "@/dto/FormConfig";
44
import { useAvailableModels } from "@/contexts/ModelContext";
5+
import { help } from "@equinor/eds-icons";
6+
7+
const makeLabel = (modelConfig: ModelConfig) =>
8+
modelConfig.accessError ? `{modelConfig.displayName} (Access Error)` : modelConfig.displayName;
9+
10+
const ModelButton: React.FC<{
11+
modelConfig: ModelConfig;
12+
active?: boolean;
13+
setCurrentModel: (modelConfig: ModelConfig) => void;
14+
}> = ({ modelConfig, active, setCurrentModel }) => {
15+
const [open, setOpen] = useState<boolean>(false);
16+
17+
return (
18+
<Button.Group>
19+
<Button
20+
variant={active ? "outlined" : "contained"}
21+
onClick={() => {
22+
setCurrentModel(modelConfig);
23+
}}
24+
disabled={!!modelConfig.accessError}
25+
>
26+
{makeLabel(modelConfig)}
27+
</Button>
28+
<Button variant={"contained"} onClick={() => setOpen(true)}>
29+
<Icon data={help} />
30+
</Button>
31+
<Dialog open={open} onClose={() => setOpen(false)} isDismissable={true} style={{ width: "100%" }}>
32+
<Dialog.Header>
33+
<Dialog.Title>Model {modelConfig.displayName}</Dialog.Title>
34+
</Dialog.Header>
35+
<Dialog.Content>{modelConfig.description}</Dialog.Content>
36+
</Dialog>
37+
</Button.Group>
38+
);
39+
};
540

641
const ModelSelect: React.FC<{ currentModel?: ModelConfig; setCurrentModel: (model: ModelConfig) => void }> = ({
742
currentModel,
@@ -26,30 +61,18 @@ const ModelSelect: React.FC<{ currentModel?: ModelConfig; setCurrentModel: (mode
2661
);
2762
}
2863

29-
function getlabel(model: ModelConfig) {
30-
if (model.accessError) {
31-
return `${model.displayName} (accesserror)`;
32-
}
33-
return model.displayName;
34-
}
35-
3664
return (
3765
<div style={{ marginBottom: "20px" }}>
38-
<Typography bold>Select model</Typography>
39-
{models.map((model, index) => (
40-
<div key={index}>
41-
<Radio
42-
label={getlabel(model)}
43-
name="model"
44-
value={model.modelId}
45-
checked={model.modelId === currentModel?.modelId}
46-
onChange={() => {
47-
setCurrentModel(model);
48-
}}
49-
disabled={!!model.accessError}
66+
<div style={{ display: "flex", gap: "16px", flexWrap: "wrap" }}>
67+
{models.map((model, index) => (
68+
<ModelButton
69+
modelConfig={model}
70+
key={index}
71+
active={model.modelId === currentModel?.modelId}
72+
setCurrentModel={setCurrentModel}
5073
/>
51-
</div>
52-
))}
74+
))}
75+
</div>
5376
</div>
5477
);
5578
};

frontend/src/components/Step.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from "react";
2+
import { Typography } from "@equinor/eds-core-react";
3+
import { tokens } from "@equinor/eds-tokens";
4+
import styled from "styled-components";
5+
6+
const color = tokens.colors.interactive.focus.rgba;
7+
8+
const Head = styled(Typography)`
9+
width: 100%;
10+
height: 48px;
11+
display: flex;
12+
gap: 16px;
13+
align-items: center;
14+
15+
margin-top: 32px;
16+
17+
svg {
18+
flex-shrink: 0;
19+
}
20+
21+
hr {
22+
flex-grow: 1;
23+
width: 100%;
24+
height: 2px;
25+
background-color: ${color};
26+
border: 0;
27+
}
28+
`;
29+
30+
const Description = styled(Typography)`
31+
margin-left: 48px;
32+
margin-bottom: 16px;
33+
`;
34+
35+
const Step: React.FC<{ step?: number; title: string; description?: string }> = ({ step, title, description }) => (
36+
<div>
37+
<Head variant="h2">
38+
<div
39+
style={{
40+
minWidth: "24px",
41+
maxWidth: "24px",
42+
minHeight: "24px",
43+
maxHeight: "24px",
44+
textAlign: "center",
45+
lineHeight: "24px",
46+
fontSize: "16px",
47+
fontWeight: "bold",
48+
borderStyle: "solid",
49+
borderRadius: "50%",
50+
borderColor: color,
51+
borderWidth: "4px",
52+
}}
53+
>
54+
{step}
55+
</div>
56+
{title}
57+
<hr />
58+
</Head>
59+
<Description variant="body_short">{description}</Description>
60+
</div>
61+
);
62+
63+
export default Step;

frontend/src/components/styles.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import styled from "styled-components";
2+
3+
/// A reactive container that is meant to be the main content of the page.
4+
/// It has a fixed width and is centered on desktop, but occupies the full screen on mobile.
5+
export const MainContainer = styled.div`
6+
margin: 0 16px;
7+
8+
@media (min-width: 968px) {
9+
margin: 0 auto;
10+
width: 968px;
11+
}
12+
`;
13+
14+
export const Columns = styled.div`
15+
@media (min-width: 968px) {
16+
display: flex;
17+
flex-direction: row;
18+
gap: 16px;
19+
20+
> * {
21+
flex-grow: 1;
22+
}
23+
}
24+
`;

0 commit comments

Comments
 (0)