Usable proof of concept of generated variables

This commit is contained in:
Daniel Dunn
2025-04-02 12:41:18 -06:00
parent 0d025a90af
commit 3264640a07
13 changed files with 358 additions and 53 deletions

View File

@@ -0,0 +1,62 @@
export default {
title: 'American Wire Gauge',
columns: [
{
diameter: {
title: 'Diameter',
type: 'number',
unit: 'mm'
}
}
],
data: {
'0000': { diameter: 11.684 },
'000': { diameter: 10.405 },
'00': { diameter: 9.266 },
'0': { diameter: 8.251 },
'(4/0)': { diameter: 11.684 },
'(3/0)': { diameter: 10.405 },
'(2/0)': { diameter: 9.266 },
'(1/0)': { diameter: 8.251 },
'1': { diameter: 7.348 },
'2': { diameter: 6.544 },
'3': { diameter: 5.827 },
'4': { diameter: 5.189 },
'5': { diameter: 4.621 },
'6': { diameter: 4.115 },
'7': { diameter: 3.665 },
'8': { diameter: 3.264 },
'9': { diameter: 2.906 },
'10': { diameter: 2.588 },
'11': { diameter: 2.305 },
'12': { diameter: 2.053 },
'13': { diameter: 1.828 },
'14': { diameter: 1.628 },
'15': { diameter: 1.45 },
'16': { diameter: 1.291 },
'17': { diameter: 1.15 },
'18': { diameter: 1.024 },
'19': { diameter: 0.912 },
'20': { diameter: 0.812 },
'21': { diameter: 0.723 },
'22': { diameter: 0.644 },
'23': { diameter: 0.573 },
'24': { diameter: 0.511 },
'25': { diameter: 0.455 },
'26': { diameter: 0.405 },
'27': { diameter: 0.361 },
'28': { diameter: 0.321 },
'29': { diameter: 0.286 },
'30': { diameter: 0.255 },
'31': { diameter: 0.227 },
'32': { diameter: 0.202 },
'33': { diameter: 0.18 },
'34': { diameter: 0.16 },
'35': { diameter: 0.143 },
'36': { diameter: 0.127 },
'37': { diameter: 0.113 },
'38': { diameter: 0.101 },
'39': { diameter: 0.0897 },
'40': { diameter: 0.0799 }
}
};

View File

@@ -0,0 +1,3 @@
export const allDataTables: { [key: string]: { [key: string]: any } } = {
'american-wire-gauge': {}
};

View File

@@ -0,0 +1,20 @@
export default {
title: 'Material Electrical Properties',
columns: [
{
resistivity_20c: {
title: 'Resistivity at 20°C',
type: 'number',
unit: 'Ω/m'
}
}
],
data: {
copper: {
resistivity: 1.68e-8
},
aluminum: {
resistivity: 2.82e-8
}
}
};

17
src/datatables/index.ts Normal file
View File

@@ -0,0 +1,17 @@
import type { DataTable } from './types.ts';
import { allDataTables } from './data/index';
export async function getDataTable(name: string): Promise<DataTable> {
const x = await import(`./${name}`);
return x.default;
}
export async function listDataTables(): Promise<{ name: string }[]> {
const x: { name: string }[] = [];
for (const key in allDataTables) {
x.push({ name: key });
}
return x;
}
export { DataTable };

19
src/datatables/types.ts Normal file
View File

@@ -0,0 +1,19 @@
/*
Represents a set of rows indexed by a key.
Used for calculator presets
*/
export interface DataTable {
[key: string]: {
title: string;
/* A JSON schema properties */
columns: {
[key: string]: any;
};
data: {
[key: string]: {
[key: string]: any;
};
};
};
}

View File

@@ -0,0 +1,3 @@
import ohmslaw from './ohms_law';
export default [ohmslaw];

View File

@@ -0,0 +1,29 @@
import type { GenericCalcType } from './types';
const ohmsLawCalc: GenericCalcType = {
title: "Ohm's Law",
name: 'ohms-law',
formula: 'V = I * R',
selections: [],
variables: [
{
name: 'V',
title: 'Voltage',
unit: 'V',
default: 5
},
{
name: 'I',
title: 'Current',
unit: 'A',
default: 1
},
{
name: 'R',
title: 'Resistance',
unit: 'Ω'
}
]
};
export default ohmsLawCalc;

View File

@@ -0,0 +1,21 @@
export interface GenericCalcType {
title: string;
name: string;
formula: string;
selections?: {
title: string;
source: string;
default: string;
bind: {
[key: string]: string;
};
}[];
variables: {
name: string;
title: string;
unit: string;
// If absence, assume it's the default target var
default?: number;
}[];
}

View File

@@ -1,6 +1,7 @@
import {
Box,
InputLabel,
Autocomplete,
TextField,
Radio,
Table,
TableBody,
@@ -13,54 +14,41 @@ import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import ToolTextResult from '@components/result/ToolTextResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { GetGroupsType, UpdateField } from '@components/options/ToolOptions';
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';
const ohmsLawCalc: {
name: string;
formula: string;
variables: {
name: string;
title: string;
unit: string;
}[];
} = {
name: "Ohm's Law Calculator",
formula: 'V = I * R',
variables: [
{
name: 'V',
title: 'Voltage',
unit: 'V'
},
{
name: 'I',
title: 'Current',
unit: 'A'
},
{
name: 'R',
title: 'Resistance',
unit: 'Ω'
}
]
};
export default function makeTool(): React.JSXElementConstructor<ToolComponentProps> {
export default async function makeTool(
calcData: GenericCalcType
): Promise<React.JSXElementConstructor<ToolComponentProps>> {
const initialValues: InitialValuesType = {
outputVariable: '',
vars: {}
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<string>('');
const [shortResult, setShortResult] = useState<string>('');
// For UX purposes we need to track what vars are
const [valsBoundToPreset, setValsBoundToPreset] = useState<{
[key: string]: string;
}>({});
const updateVarField = (
name: string,
value: number,
@@ -83,12 +71,89 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
updateFieldFunc('outputVariable', varName);
};
const handleSelectedPresetChange = (
selection: string,
preset: string,
currentValues: InitialValuesType,
updateFieldFunc: UpdateField<InitialValuesType>
) => {
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 != '<custom>') {
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 == '<custom>') 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 (
<ToolContent
title={title}
inputComponent={null}
resultComponent={
<ToolTextResult title={ohmsLawCalc.name} value={result} />
<ToolTextResult title={calcData.title} value={result} />
}
initialValues={initialValues}
toolInfo={{
@@ -97,6 +162,50 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
'Common mathematical equations that can be used in calculations.'
}}
getGroups={({ values, updateField }) => [
{
title: 'Presets',
component: (
<Box>
<Table>
<TableHead>
<TableRow>
<TableCell>Option</TableCell>
<TableCell>Value</TableCell>
</TableRow>
</TableHead>
<TableBody>
{calcData.selections?.map((preset) => (
<TableRow key={preset.title}>
<TableCell>{preset.title}</TableCell>
<TableCell>
<Autocomplete
disablePortal
id="combo-box-demo"
value={values.presets[preset.title]}
options={Object.keys(
dataTables[preset.source].data
).sort()}
sx={{ width: 300 }}
onChange={(event, newValue) => {
handleSelectedPresetChange(
preset.title,
newValue || '',
values,
updateField
);
}}
renderInput={(params) => (
<TextField {...params} label="Preset" />
)}
></Autocomplete>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
)
},
{
title: 'Input Variables',
component: (
@@ -110,20 +219,23 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
</TableRow>
</TableHead>
<TableBody>
{ohmsLawCalc.variables.map((variable) => (
{calcData.variables.map((variable) => (
<TableRow key={variable.name}>
<TableCell>{variable.name}</TableCell>
<TableCell>
<TextFieldWithDesc
title={variable.title}
sx={{ width: '25ch' }}
description=""
description={valsBoundToPreset[variable.name] || ''}
value={
values.outputVariable === variable.name
? shortResult
: values.vars[variable.name]?.value || NaN
}
disabled={values.outputVariable === variable.name}
disabled={
values.outputVariable === variable.name ||
valsBoundToPreset[variable.name] !== undefined
}
onOwnChange={(val) =>
updateVarField(
variable.name,
@@ -161,7 +273,7 @@ export default function makeTool(): React.JSXElementConstructor<ToolComponentPro
setResult('Please select a solve for variable');
return;
}
let expr = nerdamer(ohmsLawCalc.formula);
let expr = nerdamer(calcData.formula);
Object.keys(values.vars).forEach((key) => {
if (key === values.outputVariable) return;

View File

@@ -1,17 +1,32 @@
import { defineTool } from '@tools/defineTool';
import { DefinedTool, defineTool } from '@tools/defineTool';
import { lazy } from 'react';
import type { GenericCalcType } from './data/types';
import allGenericCalcs from './data/index';
async function importComponent() {
async function importComponent(data: GenericCalcType) {
const x = await import('./index');
return { default: x.default() };
return { default: await x.default(data) };
}
export const tool = defineTool('number', {
name: 'Generic calc',
path: 'generic-calc',
icon: '',
description: '',
shortDescription: '',
keywords: ['generic', 'calc'],
longDescription: '',
component: lazy(importComponent)
const tools: DefinedTool[] = [];
allGenericCalcs.forEach((x) => {
async function importComponent2() {
return await importComponent(x);
}
tools.push(
defineTool('number', {
name: x.title,
path: 'generic-calc/x.name',
icon: '',
description: '',
shortDescription: '',
keywords: ['generic', 'calc'],
longDescription: '',
component: lazy(importComponent2)
})
);
});
export { tools };

View File

@@ -6,5 +6,9 @@ export type InitialValuesType = {
};
};
// Track preset selections
presets: {
[key: string]: string;
};
outputVariable: string;
};

View File

@@ -1,10 +1,10 @@
import { tool as numberSum } from './sum/meta';
import { tool as numberGenerate } from './generate/meta';
import { tool as numberArithmeticSequence } from './arithmetic-sequence/meta';
import { tool as genericCalc } from './generic-calc/meta';
import { tools as genericCalcTools } from './generic-calc/meta';
export const numberTools = [
numberSum,
numberGenerate,
numberArithmeticSequence,
genericCalc
...genericCalcTools
];