import { Box, Autocomplete, TextField, Radio, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; import React, { useState } from 'react'; import ToolContent from '@components/ToolContent'; import { ToolComponentProps } from '@tools/defineTool'; import ToolTextResult from '@components/result/ToolTextResult'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import { UpdateField } from '@components/options/ToolOptions'; import { InitialValuesType } from './types'; import type { GenericCalcType } from './data/types'; import type { DataTable } from 'datatables'; import { getDataTable } from 'datatables'; import nerdamer from 'nerdamer'; import 'nerdamer/Algebra'; import 'nerdamer/Solve'; import 'nerdamer/Calculus'; export default async function makeTool( calcData: GenericCalcType ): Promise> { const initialValues: InitialValuesType = { outputVariable: '', vars: {}, presets: {} }; const dataTables: { [key: string]: DataTable } = {}; for (const selection of calcData.selections || []) { dataTables[selection.source] = await getDataTable(selection.source); } return function GenericCalc({ title }: ToolComponentProps) { const [result, setResult] = useState(''); const [shortResult, setShortResult] = useState(''); // For UX purposes we need to track what vars are const [valsBoundToPreset, setValsBoundToPreset] = useState<{ [key: string]: string; }>({}); const updateVarField = ( name: string, value: number, values: InitialValuesType, updateFieldFunc: UpdateField ) => { // Make copy const newVars = { ...values.vars }; newVars[name] = { value, unit: values.vars[name]?.unit || '' }; updateFieldFunc('vars', newVars); }; const handleSelectedTargetChange = ( varName: string, updateFieldFunc: UpdateField ) => { updateFieldFunc('outputVariable', varName); }; const handleSelectedPresetChange = ( selection: string, preset: string, currentValues: InitialValuesType, updateFieldFunc: UpdateField ) => { const newValsBoundToPreset = { ...valsBoundToPreset }; const newPresets = { ...currentValues.presets }; newPresets[selection] = preset; updateFieldFunc('presets', newPresets); // Clear old selection for (const key in valsBoundToPreset) { if (valsBoundToPreset[key] === selection) { delete newValsBoundToPreset[key]; } } const selectionData = calcData.selections?.find( (sel) => sel.title === selection ); if (preset != '') { if (selectionData) { for (const key in selectionData.bind) { newValsBoundToPreset[key] = selection; updateVarField( key, dataTables[selectionData.source].data[preset][ selectionData.bind[key] ], currentValues, updateFieldFunc ); } } else { setValsBoundToPreset(newValsBoundToPreset); throw new Error( `Preset "${preset}" is not valid for selection "${selection}"` ); } } setValsBoundToPreset(newValsBoundToPreset); }; calcData.variables.forEach((variable) => { if (variable.default === undefined) { initialValues.vars[variable.name] = { value: NaN, unit: variable.unit }; initialValues.outputVariable = variable.name; } else { initialValues.vars[variable.name] = { value: variable.default || 0, unit: variable.unit }; } }); calcData.selections?.forEach((selection) => { initialValues.presets[selection.title] = selection.default; if (selection.default == '') return; for (const key in selection.bind) { initialValues.vars[key] = { value: dataTables[selection.source].data[selection.default][ selection.bind[key] ], unit: dataTables[selection.source].cols[selection.bind[key]].unit }; valsBoundToPreset[key] = selection.default; } }); return ( } initialValues={initialValues} toolInfo={{ title: 'Common Equations', description: 'Common mathematical equations that can be used in calculations.' }} getGroups={({ values, updateField }) => [ { title: 'Presets', component: ( Option Value {calcData.selections?.map((preset) => ( {preset.title} { handleSelectedPresetChange( preset.title, newValue || '', values, updateField ); }} renderInput={(params) => ( )} > ))}
) }, { title: 'Input Variables', component: ( Variable Value Unit Solve For {calcData.variables.map((variable) => ( {variable.name} updateVarField( variable.name, parseFloat(val), values, updateField ) } type="number" /> {variable.unit} handleSelectedTargetChange( variable.name, updateField ) } /> ))}
) } ]} compute={(values) => { if (values.outputVariable === '') { setResult('Please select a solve for variable'); return; } let expr = nerdamer(calcData.formula); Object.keys(values.vars).forEach((key) => { if (key === values.outputVariable) return; expr = expr.sub(key, values.vars[key].value.toString()); }); let result: nerdamer.Expression = expr.solveFor( values.outputVariable ); // Sometimes the result is an array if (result.toDecimal === undefined) { result = (result as unknown as nerdamer.Expression[])[0]; } setResult(result.toString()); if (result) { if (values.vars[values.outputVariable] != undefined) { values.vars[values.outputVariable].value = parseFloat( result.toDecimal() ); } setShortResult(result.toDecimal()); } else { setShortResult(''); } }} /> ); }; }