optimization-dev / frontends /react /src /OptimiserControls.tsx
nhamid289's picture
Deploy optimisation from main (6727a1a)
c964813 verified
Raw History Blame Contribute Delete
4.02 kB
import { useId, useState } from "react";
import { Dices, Info, RotateCcw } from "lucide-react";
import { AllSettings, Dialog, Dropdown, IconButton, InputField, ParamControls, type ParamValue } from "@elvis/ui";
import { ALGORITHMS, algorithmById } from "./algorithms";
import type { RunEditor } from "./useOptimisation";
const helpClass = "text-sm text-ink-muted";
interface OptimiserControlsProps {
editor: RunEditor;
twoVariables: boolean;
onResetStart: () => void;
onRandomStart: () => void;
// Extra help under the start point, e.g. that the plot can be clicked
hint?: string;
}
// An optimiser's algorithm, settings and start point; used for the main run
// (Configure) and for setting up a comparison (Compare)
export default function OptimiserControls({ editor, twoVariables, onResetStart, onRandomStart, hint }: OptimiserControlsProps) {
const active = algorithmById(editor.algorithm);
const [infoOpen, setInfoOpen] = useState(false);
const id = useId();
const hasSettings = active.params.length > 0;
const changed = Object.keys(editor.params).length > 0;
function setOne(name: string, value: ParamValue) {
editor.changeParams({ ...editor.params, [name]: value });
}
function resetOne(name: string) {
editor.changeParams(Object.fromEntries(Object.entries(editor.params).filter(([key]) => key !== name)));
}
return (
<>
{/* The algorithm, with its settings' details and a reset beside it */}
<div className="flex items-center gap-2">
<div className="min-w-0 flex-1">
<Dropdown
label="Algorithm"
hideLabel
options={ALGORITHMS.map((a) => a.label)}
activeOption={active.label}
onChange={(label) => editor.changeAlgorithm(ALGORITHMS.find((a) => a.label === label)!.id)}
/>
</div>
<IconButton label={`About the ${active.label} settings`} onClick={() => setInfoOpen(true)} disabled={!hasSettings}>
<Info size={16} />
</IconButton>
<IconButton label="Reset the settings to their defaults" onClick={() => editor.changeParams({})} disabled={!changed}>
<RotateCcw size={16} />
</IconButton>
</div>
{/* Up to 4 settings side by side, each shown by its symbol */}
<ParamControls model={active} values={editor.params} onChange={editor.changeParams} inline />
<Dialog title={`${active.label} settings`} open={infoOpen} onClose={() => setInfoOpen(false)} wide>
<AllSettings model={active} values={editor.params} onChange={setOne} onResetOne={resetOne} onReset={() => editor.changeParams({})} />
</Dialog>
<div className="flex flex-col gap-2">
{/* One label for the coordinates; with two variables each box says which */}
<div role="group" aria-labelledby={`${id}-initial`} className="flex flex-col gap-1">
<span id={`${id}-initial`} className="text-sm text-gray-700">Initial point</span>
<div className="flex items-center gap-2">
<div className="min-w-0 flex-1">
<InputField label="Initial x" hideLabel prefix={twoVariables ? "x" : undefined} value={editor.startText[0]} onChange={(t) => editor.changeStart(0, t)} mono />
</div>
{twoVariables && (
<div className="min-w-0 flex-1">
<InputField label="Initial y" hideLabel prefix="y" value={editor.startText[1]} onChange={(t) => editor.changeStart(1, t)} mono />
</div>
)}
<IconButton label="Random initial point" onClick={onRandomStart}>
<Dices size={16} />
</IconButton>
<IconButton label="Reset the initial point" onClick={onResetStart}>
<RotateCcw size={16} />
</IconButton>
</div>
</div>
{!editor.startValid && <p className="text-red-600 font-semibold">The initial point must be numbers.</p>}
{hint && <p className={helpClass}>{hint}</p>}
</div>
</>
);
}