From 0d025a90af9a7da0f714130c5922b02058bd990a Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Wed, 2 Apr 2025 00:13:17 -0600 Subject: [PATCH 01/53] Initial work on generating tools from equations and descriptions --- .../omni-tools/src/pages/tools/index.ts | 1 + .../pages/tools/number/calculators/index.ts | 1 + .../src/pages/tools/number/index.ts | 1 + package-lock.json | 9 + package.json | 1 + .../generic-calc/generic-calc.service.test.ts | 6 + src/pages/tools/number/generic-calc/index.tsx | 196 ++++++++++++++++++ src/pages/tools/number/generic-calc/meta.ts | 17 ++ .../tools/number/generic-calc/service.ts | 5 + src/pages/tools/number/generic-calc/types.ts | 10 + src/pages/tools/number/index.ts | 9 +- 11 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 home/daniel/Projects/omni-tools/src/pages/tools/index.ts create mode 100644 home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts create mode 100644 home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts create mode 100644 src/pages/tools/number/generic-calc/generic-calc.service.test.ts create mode 100644 src/pages/tools/number/generic-calc/index.tsx create mode 100644 src/pages/tools/number/generic-calc/meta.ts create mode 100644 src/pages/tools/number/generic-calc/service.ts create mode 100644 src/pages/tools/number/generic-calc/types.ts diff --git a/home/daniel/Projects/omni-tools/src/pages/tools/index.ts b/home/daniel/Projects/omni-tools/src/pages/tools/index.ts new file mode 100644 index 0000000..71e8eea --- /dev/null +++ b/home/daniel/Projects/omni-tools/src/pages/tools/index.ts @@ -0,0 +1 @@ +export const toolsTools = []; diff --git a/home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts b/home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts new file mode 100644 index 0000000..f7a9c9d --- /dev/null +++ b/home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts @@ -0,0 +1 @@ +export const calculatorsTools = []; diff --git a/home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts b/home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts new file mode 100644 index 0000000..90c9f0e --- /dev/null +++ b/home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts @@ -0,0 +1 @@ +export const numberTools = []; diff --git a/package-lock.json b/package-lock.json index b2074f9..6511848 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "lodash": "^4.17.21", "mime": "^4.0.6", "morsee": "^1.0.9", + "nerdamer": "^1.1.13", "notistack": "^3.0.1", "omggif": "^1.0.10", "pdf-lib": "^1.17.1", @@ -7820,6 +7821,14 @@ "is-buffer": "^1.0.2" } }, + "node_modules/nerdamer": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/nerdamer/-/nerdamer-1.1.13.tgz", + "integrity": "sha512-kQGQYd42eQpKDOnU8ZnRKF47c+gK6jVC46eUchrABsovtFruHvsjyjBO32jck8QnpZE5z5R8HQw72hQX9Oq2MQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", diff --git a/package.json b/package.json index 68dfa2d..cd950ab 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "lodash": "^4.17.21", "mime": "^4.0.6", "morsee": "^1.0.9", + "nerdamer": "^1.1.13", "notistack": "^3.0.1", "omggif": "^1.0.10", "pdf-lib": "^1.17.1", diff --git a/src/pages/tools/number/generic-calc/generic-calc.service.test.ts b/src/pages/tools/number/generic-calc/generic-calc.service.test.ts new file mode 100644 index 0000000..4152224 --- /dev/null +++ b/src/pages/tools/number/generic-calc/generic-calc.service.test.ts @@ -0,0 +1,6 @@ +import { expect, describe, it } from 'vitest'; +// import { main } from './service'; +// +// describe('generic-calc', () => { +// +// }) diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx new file mode 100644 index 0000000..4cabf41 --- /dev/null +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -0,0 +1,196 @@ +import { + Box, + InputLabel, + 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 { GetGroupsType, UpdateField } from '@components/options/ToolOptions'; +import { InitialValuesType } from './types'; + +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 { + const initialValues: InitialValuesType = { + outputVariable: '', + vars: {} + }; + + return function GenericCalc({ title }: ToolComponentProps) { + const [result, setResult] = useState(''); + const [shortResult, setShortResult] = useState(''); + + 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); + }; + + return ( + + } + initialValues={initialValues} + toolInfo={{ + title: 'Common Equations', + description: + 'Common mathematical equations that can be used in calculations.' + }} + getGroups={({ values, updateField }) => [ + { + title: 'Input Variables', + component: ( + + + + Variable + Value + Unit + Solve For + + + + {ohmsLawCalc.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(ohmsLawCalc.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(''); + } + }} + /> + ); + }; +} diff --git a/src/pages/tools/number/generic-calc/meta.ts b/src/pages/tools/number/generic-calc/meta.ts new file mode 100644 index 0000000..aff3a72 --- /dev/null +++ b/src/pages/tools/number/generic-calc/meta.ts @@ -0,0 +1,17 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +async function importComponent() { + const x = await import('./index'); + return { default: x.default() }; +} +export const tool = defineTool('number', { + name: 'Generic calc', + path: 'generic-calc', + icon: '', + description: '', + shortDescription: '', + keywords: ['generic', 'calc'], + longDescription: '', + component: lazy(importComponent) +}); diff --git a/src/pages/tools/number/generic-calc/service.ts b/src/pages/tools/number/generic-calc/service.ts new file mode 100644 index 0000000..886ff10 --- /dev/null +++ b/src/pages/tools/number/generic-calc/service.ts @@ -0,0 +1,5 @@ +import { InitialValuesType } from './types'; + +export function main(input: string, options: InitialValuesType): string { + return input + 'pp'; +} diff --git a/src/pages/tools/number/generic-calc/types.ts b/src/pages/tools/number/generic-calc/types.ts new file mode 100644 index 0000000..8953230 --- /dev/null +++ b/src/pages/tools/number/generic-calc/types.ts @@ -0,0 +1,10 @@ +export type InitialValuesType = { + vars: { + [key: string]: { + value: number; + unit: string; + }; + }; + + outputVariable: string; +}; diff --git a/src/pages/tools/number/index.ts b/src/pages/tools/number/index.ts index c2ca546..c03df53 100644 --- a/src/pages/tools/number/index.ts +++ b/src/pages/tools/number/index.ts @@ -1,5 +1,10 @@ import { tool as numberSum } from './sum/meta'; import { tool as numberGenerate } from './generate/meta'; import { tool as numberArithmeticSequence } from './arithmetic-sequence/meta'; - -export const numberTools = [numberSum, numberGenerate, numberArithmeticSequence]; +import { tool as genericCalc } from './generic-calc/meta'; +export const numberTools = [ + numberSum, + numberGenerate, + numberArithmeticSequence, + genericCalc +]; From 3264640a07ce92a8973ba31be1caff4e8a692750 Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Wed, 2 Apr 2025 12:41:18 -0600 Subject: [PATCH 02/53] Usable proof of concept of generated variables --- src/datatables/data/american_wire_gauge.ts | 62 ++++++ src/datatables/data/index.ts | 3 + .../data/material_electrical_properties.ts | 20 ++ src/datatables/index.ts | 17 ++ src/datatables/types.ts | 19 ++ .../tools/number/generic-calc/data/index.ts | 3 + .../number/generic-calc/data/ohms_law.ts | 29 +++ .../tools/number/generic-calc/data/types.ts | 21 ++ .../generic-calc/data/wire_voltage_drop.ts | 0 src/pages/tools/number/generic-calc/index.tsx | 190 ++++++++++++++---- src/pages/tools/number/generic-calc/meta.ts | 39 ++-- src/pages/tools/number/generic-calc/types.ts | 4 + src/pages/tools/number/index.ts | 4 +- 13 files changed, 358 insertions(+), 53 deletions(-) create mode 100644 src/datatables/data/american_wire_gauge.ts create mode 100644 src/datatables/data/index.ts create mode 100644 src/datatables/data/material_electrical_properties.ts create mode 100644 src/datatables/index.ts create mode 100644 src/datatables/types.ts create mode 100644 src/pages/tools/number/generic-calc/data/index.ts create mode 100644 src/pages/tools/number/generic-calc/data/ohms_law.ts create mode 100644 src/pages/tools/number/generic-calc/data/types.ts create mode 100644 src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts diff --git a/src/datatables/data/american_wire_gauge.ts b/src/datatables/data/american_wire_gauge.ts new file mode 100644 index 0000000..218ab9d --- /dev/null +++ b/src/datatables/data/american_wire_gauge.ts @@ -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 } + } +}; diff --git a/src/datatables/data/index.ts b/src/datatables/data/index.ts new file mode 100644 index 0000000..d9efaca --- /dev/null +++ b/src/datatables/data/index.ts @@ -0,0 +1,3 @@ +export const allDataTables: { [key: string]: { [key: string]: any } } = { + 'american-wire-gauge': {} +}; diff --git a/src/datatables/data/material_electrical_properties.ts b/src/datatables/data/material_electrical_properties.ts new file mode 100644 index 0000000..8b78de9 --- /dev/null +++ b/src/datatables/data/material_electrical_properties.ts @@ -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 + } + } +}; diff --git a/src/datatables/index.ts b/src/datatables/index.ts new file mode 100644 index 0000000..5c3ff98 --- /dev/null +++ b/src/datatables/index.ts @@ -0,0 +1,17 @@ +import type { DataTable } from './types.ts'; +import { allDataTables } from './data/index'; + +export async function getDataTable(name: string): Promise { + 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 }; diff --git a/src/datatables/types.ts b/src/datatables/types.ts new file mode 100644 index 0000000..37f4f48 --- /dev/null +++ b/src/datatables/types.ts @@ -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; + }; + }; + }; +} diff --git a/src/pages/tools/number/generic-calc/data/index.ts b/src/pages/tools/number/generic-calc/data/index.ts new file mode 100644 index 0000000..727349c --- /dev/null +++ b/src/pages/tools/number/generic-calc/data/index.ts @@ -0,0 +1,3 @@ +import ohmslaw from './ohms_law'; + +export default [ohmslaw]; diff --git a/src/pages/tools/number/generic-calc/data/ohms_law.ts b/src/pages/tools/number/generic-calc/data/ohms_law.ts new file mode 100644 index 0000000..25bbc66 --- /dev/null +++ b/src/pages/tools/number/generic-calc/data/ohms_law.ts @@ -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; diff --git a/src/pages/tools/number/generic-calc/data/types.ts b/src/pages/tools/number/generic-calc/data/types.ts new file mode 100644 index 0000000..084413b --- /dev/null +++ b/src/pages/tools/number/generic-calc/data/types.ts @@ -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; + }[]; +} diff --git a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 4cabf41..2b8b69e 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -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 { +export default async function makeTool( + calcData: GenericCalcType +): Promise> { 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(''); 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, @@ -83,12 +71,89 @@ export default function makeTool(): React.JSXElementConstructor + ) => { + 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={{ @@ -97,6 +162,50 @@ export default function makeTool(): React.JSXElementConstructor [ + { + title: 'Presets', + component: ( + + + + + Option + Value + + + + {calcData.selections?.map((preset) => ( + + {preset.title} + + { + handleSelectedPresetChange( + preset.title, + newValue || '', + values, + updateField + ); + }} + renderInput={(params) => ( + + )} + > + + + ))} + +
+
+ ) + }, { title: 'Input Variables', component: ( @@ -110,20 +219,23 @@ export default function makeTool(): React.JSXElementConstructor - {ohmsLawCalc.variables.map((variable) => ( + {calcData.variables.map((variable) => ( {variable.name} updateVarField( variable.name, @@ -161,7 +273,7 @@ export default function makeTool(): React.JSXElementConstructor { if (key === values.outputVariable) return; diff --git a/src/pages/tools/number/generic-calc/meta.ts b/src/pages/tools/number/generic-calc/meta.ts index aff3a72..f095164 100644 --- a/src/pages/tools/number/generic-calc/meta.ts +++ b/src/pages/tools/number/generic-calc/meta.ts @@ -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 }; diff --git a/src/pages/tools/number/generic-calc/types.ts b/src/pages/tools/number/generic-calc/types.ts index 8953230..28f6076 100644 --- a/src/pages/tools/number/generic-calc/types.ts +++ b/src/pages/tools/number/generic-calc/types.ts @@ -6,5 +6,9 @@ export type InitialValuesType = { }; }; + // Track preset selections + presets: { + [key: string]: string; + }; outputVariable: string; }; diff --git a/src/pages/tools/number/index.ts b/src/pages/tools/number/index.ts index c03df53..f26f6fe 100644 --- a/src/pages/tools/number/index.ts +++ b/src/pages/tools/number/index.ts @@ -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 ]; From db2e1810a9a7fa5f234f53f983890646e6893a5e Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Wed, 2 Apr 2025 13:38:12 -0600 Subject: [PATCH 03/53] Wire gauge calc --- src/datatables/data/american_wire_gauge.ts | 62 --------------- src/datatables/data/index.ts | 11 ++- .../data/material_electrical_properties.ts | 8 +- src/datatables/data/wire_gauge.ts | 75 +++++++++++++++++++ src/datatables/index.ts | 19 ++--- src/datatables/types.ts | 16 ++-- .../tools/number/generic-calc/data/index.ts | 3 +- .../generic-calc/data/wire_voltage_drop.ts | 59 +++++++++++++++ src/pages/tools/number/generic-calc/index.tsx | 52 +++++++------ src/pages/tools/number/generic-calc/meta.ts | 2 +- 10 files changed, 198 insertions(+), 109 deletions(-) delete mode 100644 src/datatables/data/american_wire_gauge.ts create mode 100644 src/datatables/data/wire_gauge.ts diff --git a/src/datatables/data/american_wire_gauge.ts b/src/datatables/data/american_wire_gauge.ts deleted file mode 100644 index 218ab9d..0000000 --- a/src/datatables/data/american_wire_gauge.ts +++ /dev/null @@ -1,62 +0,0 @@ -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 } - } -}; diff --git a/src/datatables/data/index.ts b/src/datatables/data/index.ts index d9efaca..c5c5420 100644 --- a/src/datatables/data/index.ts +++ b/src/datatables/data/index.ts @@ -1,3 +1,10 @@ -export const allDataTables: { [key: string]: { [key: string]: any } } = { - 'american-wire-gauge': {} +import type { DataTable } from '../types'; +import wiregauge from './wire_gauge'; +import material_electrical_properties from './material_electrical_properties'; + +const allDataTables: { [key: string]: DataTable } = { + 'wire-gauge': wiregauge, + 'material-electrical-properties': material_electrical_properties }; + +export default allDataTables; diff --git a/src/datatables/data/material_electrical_properties.ts b/src/datatables/data/material_electrical_properties.ts index 8b78de9..912d016 100644 --- a/src/datatables/data/material_electrical_properties.ts +++ b/src/datatables/data/material_electrical_properties.ts @@ -10,11 +10,11 @@ export default { } ], data: { - copper: { - resistivity: 1.68e-8 + Copper: { + resistivity_20c: 1.68e-8 }, - aluminum: { - resistivity: 2.82e-8 + Aluminum: { + resistivity_20c: 2.82e-8 } } }; diff --git a/src/datatables/data/wire_gauge.ts b/src/datatables/data/wire_gauge.ts new file mode 100644 index 0000000..94b2a19 --- /dev/null +++ b/src/datatables/data/wire_gauge.ts @@ -0,0 +1,75 @@ +import type { DataTable } from '../types'; + +const data: DataTable = { + title: 'American Wire Gauge', + columns: [ + { + diameter: { + title: 'Diameter', + type: 'number', + unit: 'mm' + }, + area: { + title: 'Area', + type: 'number', + unit: 'mm^2' + } + } + ], + data: { + '0000 AWG': { diameter: 11.684 }, + '000 AWG': { diameter: 10.405 }, + '00 AWG': { diameter: 9.266 }, + '0 AWG': { diameter: 8.251 }, + '(4/0) AWG': { diameter: 11.684 }, + '(3/0) AWG': { diameter: 10.405 }, + '(2/0) AWG': { diameter: 9.266 }, + '(1/0) AWG': { diameter: 8.251 }, + '1 AWG': { diameter: 7.348 }, + '2 AWG': { diameter: 6.544 }, + '3 AWG': { diameter: 5.827 }, + '4 AWG': { diameter: 5.189 }, + '5 AWG': { diameter: 4.621 }, + '6 AWG': { diameter: 4.115 }, + '7 AWG': { diameter: 3.665 }, + '8 AWG': { diameter: 3.264 }, + '9 AWG': { diameter: 2.906 }, + '10 AWG': { diameter: 2.588 }, + '11 AWG': { diameter: 2.305 }, + '12 AWG': { diameter: 2.053 }, + '13 AWG': { diameter: 1.828 }, + '14 AWG': { diameter: 1.628 }, + '15 AWG': { diameter: 1.45 }, + '16 AWG': { diameter: 1.291 }, + '17 AWG': { diameter: 1.15 }, + '18 AWG': { diameter: 1.024 }, + '19 AWG': { diameter: 0.912 }, + '20 AWG': { diameter: 0.812 }, + '21 AWG': { diameter: 0.723 }, + '22 AWG': { diameter: 0.644 }, + '23 AWG': { diameter: 0.573 }, + '24 AWG': { diameter: 0.511 }, + '25 AWG': { diameter: 0.455 }, + '26 AWG': { diameter: 0.405 }, + '27 AWG': { diameter: 0.361 }, + '28 AWG': { diameter: 0.321 }, + '29 AWG': { diameter: 0.286 }, + '30 AWG': { diameter: 0.255 }, + '31 AWG': { diameter: 0.227 }, + '32 AWG': { diameter: 0.202 }, + '33 AWG': { diameter: 0.18 }, + '34 AWG': { diameter: 0.16 }, + '35 AWG': { diameter: 0.143 }, + '36 AWG': { diameter: 0.127 }, + '37 AWG': { diameter: 0.113 }, + '38 AWG': { diameter: 0.101 }, + '39 AWG': { diameter: 0.0897 }, + '40 AWG': { diameter: 0.0799 } + } +}; + +for (const key in data.data) { + data.data[key].area = Math.PI * data.data[key].diameter ** 2; +} + +export default data; diff --git a/src/datatables/index.ts b/src/datatables/index.ts index 5c3ff98..37174b2 100644 --- a/src/datatables/index.ts +++ b/src/datatables/index.ts @@ -1,17 +1,18 @@ import type { DataTable } from './types.ts'; -import { allDataTables } from './data/index'; export async function getDataTable(name: string): Promise { - const x = await import(`./${name}`); - return x.default; + const allDataTables = (await import('./data/index')).default; + return allDataTables[name]; } -export async function listDataTables(): Promise<{ name: string }[]> { - const x: { name: string }[] = []; - for (const key in allDataTables) { - x.push({ name: key }); - } - return x; +/* Used in case later we want any kind of computed extra data */ +export function dataTableLookup(table: DataTable, key: string): any { + return table.data[key]; +} + +export async function listDataTables(): Promise<{ [name: string]: DataTable }> { + const allDataTables = (await import('./data/index')).default; + return allDataTables; } export { DataTable }; diff --git a/src/datatables/types.ts b/src/datatables/types.ts index 37f4f48..c2199d4 100644 --- a/src/datatables/types.ts +++ b/src/datatables/types.ts @@ -4,16 +4,14 @@ Used for calculator presets */ export interface DataTable { - [key: string]: { - title: string; - /* A JSON schema properties */ - columns: { + title: string; + /* A JSON schema properties */ + columns: { + [key: string]: any; + }; + data: { + [key: string]: { [key: string]: any; }; - data: { - [key: string]: { - [key: string]: any; - }; - }; }; } diff --git a/src/pages/tools/number/generic-calc/data/index.ts b/src/pages/tools/number/generic-calc/data/index.ts index 727349c..9249a30 100644 --- a/src/pages/tools/number/generic-calc/data/index.ts +++ b/src/pages/tools/number/generic-calc/data/index.ts @@ -1,3 +1,4 @@ import ohmslaw from './ohms_law'; +import voltagedropinwire from './wire_voltage_drop'; -export default [ohmslaw]; +export default [ohmslaw, voltagedropinwire]; diff --git a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts index e69de29..f0338a2 100644 --- a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts +++ b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts @@ -0,0 +1,59 @@ +import type { GenericCalcType } from './types'; +const voltagedropinwire: GenericCalcType = { + title: 'Round trip voltage drop in cable', + name: 'cable-voltage-drop', + formula: 'x = (((p * L) / (A/10**6) ) *2) * I**2', + selections: [ + { + title: 'Material', + source: 'material-electrical-properties', + default: 'Copper', + bind: { + p: 'resistivity_20c' + } + }, + + { + title: 'Wire Gauge', + source: 'wire-gauge', + default: '24 AWG', + bind: { + A: 'area' + } + } + ], + variables: [ + { + name: 'L', + title: 'Length', + unit: 'm', + default: 1 + }, + { + name: 'A', + title: 'Wire Area', + unit: 'mm', + default: 1 + }, + + { + name: 'I', + title: 'Current', + unit: 'A', + default: 1 + }, + { + name: 'p', + title: 'Resistivity', + unit: 'Ω/m3', + default: 1 + }, + { + name: 'x', + title: 'Voltage Drop', + unit: 'V' + } + ] +}; + +export default voltagedropinwire; diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 2b8b69e..473c85d 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -18,7 +18,7 @@ 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 { getDataTable, dataTableLookup } from 'datatables'; import nerdamer from 'nerdamer'; import 'nerdamer/Algebra'; @@ -77,8 +77,6 @@ export default async function makeTool( currentValues: InitialValuesType, updateFieldFunc: UpdateField ) => { - const newValsBoundToPreset = { ...valsBoundToPreset }; - const newPresets = { ...currentValues.presets }; newPresets[selection] = preset; updateFieldFunc('presets', newPresets); @@ -86,7 +84,7 @@ export default async function makeTool( // Clear old selection for (const key in valsBoundToPreset) { if (valsBoundToPreset[key] === selection) { - delete newValsBoundToPreset[key]; + delete valsBoundToPreset[key]; } } @@ -94,13 +92,19 @@ export default async function makeTool( (sel) => sel.title === selection ); - if (preset != '') { + if (preset && preset != '') { if (selectionData) { for (const key in selectionData.bind) { - newValsBoundToPreset[key] = selection; + valsBoundToPreset[key] = selection; + + if (currentValues.outputVariable === key) { + handleSelectedTargetChange('', updateFieldFunc); + } + updateVarField( key, - dataTables[selectionData.source].data[preset][ + + dataTableLookup(dataTables[selectionData.source], preset)[ selectionData.bind[key] ], currentValues, @@ -108,14 +112,11 @@ export default async function makeTool( ); } } else { - setValsBoundToPreset(newValsBoundToPreset); throw new Error( `Preset "${preset}" is not valid for selection "${selection}"` ); } } - - setValsBoundToPreset(newValsBoundToPreset); }; calcData.variables.forEach((variable) => { @@ -138,13 +139,16 @@ export default async function makeTool( 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 + value: dataTableLookup( + dataTables[selection.source], + selection.default + )[selection.bind[key]], + + unit: + dataTables[selection.source].columns[selection.bind[key]]?.unit || + '' }; - valsBoundToPreset[key] = selection.default; + valsBoundToPreset[key] = selection.title; } }); @@ -182,9 +186,12 @@ export default async function makeTool( disablePortal id="combo-box-demo" value={values.presets[preset.title]} - options={Object.keys( - dataTables[preset.source].data - ).sort()} + options={[ + '', + ...Object.keys( + dataTables[preset.source].data + ).sort() + ]} sx={{ width: 300 }} onChange={(event, newValue) => { handleSelectedPresetChange( @@ -207,7 +214,7 @@ export default async function makeTool( ) }, { - title: 'Input Variables', + title: 'Variables', component: ( @@ -221,7 +228,7 @@ export default async function makeTool( {calcData.variables.map((variable) => ( - {variable.name} + {variable.title} handleSelectedTargetChange( variable.name, diff --git a/src/pages/tools/number/generic-calc/meta.ts b/src/pages/tools/number/generic-calc/meta.ts index f095164..3254deb 100644 --- a/src/pages/tools/number/generic-calc/meta.ts +++ b/src/pages/tools/number/generic-calc/meta.ts @@ -18,7 +18,7 @@ allGenericCalcs.forEach((x) => { tools.push( defineTool('number', { name: x.title, - path: 'generic-calc/x.name', + path: 'generic-calc/' + x.name, icon: '', description: '', shortDescription: '', From 42d84dbfa0e78a79a5e14ae3c8417d623d65df0d Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Wed, 2 Apr 2025 19:22:29 -0600 Subject: [PATCH 04/53] Fix wire gauge bugs, add extra outputs to generic calc generation --- package-lock.json | 12 +++ package.json | 2 + src/components/input/NumericInputWithUnit.tsx | 78 +++++++++++++++++++ src/datatables/data/wire_gauge.ts | 2 +- .../tools/number/generic-calc/data/types.ts | 6 ++ .../generic-calc/data/wire_voltage_drop.ts | 15 +++- src/pages/tools/number/generic-calc/index.tsx | 32 ++++++++ 7 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/components/input/NumericInputWithUnit.tsx diff --git a/package-lock.json b/package-lock.json index 6511848..cfa3e2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@mui/material": "^5.15.20", "@playwright/test": "^1.45.0", "@types/ffmpeg": "^1.0.7", + "@types/js-quantities": "^1.6.6", "@types/lodash": "^4.17.5", "@types/morsee": "^1.0.2", "@types/omggif": "^1.0.5", @@ -26,6 +27,7 @@ "color": "^4.2.3", "formik": "^2.4.6", "jimp": "^0.22.12", + "js-quantities": "^1.8.0", "lint-staged": "^15.4.3", "lodash": "^4.17.21", "mime": "^4.0.6", @@ -3112,6 +3114,11 @@ "hoist-non-react-statics": "^3.3.0" } }, + "node_modules/@types/js-quantities": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/js-quantities/-/js-quantities-1.6.6.tgz", + "integrity": "sha512-k2Q8/Avj4Oz50flfTnfVGnUCkt7OYQ3U+lfQVELE/x5mdbwChZ7fM0wpUpVWzbuSL8kIYt9ZsFQ0RFNBv8T3Qw==" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -6970,6 +6977,11 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", "license": "BSD-3-Clause" }, + "node_modules/js-quantities": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/js-quantities/-/js-quantities-1.8.0.tgz", + "integrity": "sha512-swDw9RJpXACAWR16vAKoSojAsP6NI7cZjjnjKqhOyZSdybRUdmPr071foD3fejUKSU2JMHz99hflWkRWvfLTpQ==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index cd950ab..b8dd4f8 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@mui/material": "^5.15.20", "@playwright/test": "^1.45.0", "@types/ffmpeg": "^1.0.7", + "@types/js-quantities": "^1.6.6", "@types/lodash": "^4.17.5", "@types/morsee": "^1.0.2", "@types/omggif": "^1.0.5", @@ -43,6 +44,7 @@ "color": "^4.2.3", "formik": "^2.4.6", "jimp": "^0.22.12", + "js-quantities": "^1.8.0", "lint-staged": "^15.4.3", "lodash": "^4.17.21", "mime": "^4.0.6", diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx new file mode 100644 index 0000000..bef047b --- /dev/null +++ b/src/components/input/NumericInputWithUnit.tsx @@ -0,0 +1,78 @@ +import React, { useState, useEffect } from 'react'; +import { TextField, Grid } from '@mui/material'; +import Autocomplete from '@mui/material/Autocomplete'; +import Qty from 'js-quantities'; + +export default function NumericInputWithUnit(props: { + value: { value: number; unit: string }; + onChange: (value: { value: number; unit: string }) => void; +}) { + const [inputValue, setInputValue] = useState(props.value.value); + const [unit, setUnit] = useState(props.value.unit); + const [unitOptions, setUnitOptions] = useState([]); + + useEffect(() => { + try { + const kind = Qty(props.value.unit).kind(); + const units = Qty.getUnits(kind); + setUnitOptions(units); + } catch (error) { + console.error('Invalid unit kind', error); + } + }, [props.value.unit]); + + useEffect(() => { + setInputValue(props.value.value); + setUnit(props.value.unit); + }, [props.value]); + + const handleValueChange = (event: React.ChangeEvent) => { + const newValue = parseFloat(event.target.value) || 0; + setInputValue(newValue); + if (props.onChange) { + try { + const qty = Qty(newValue, unit).to('meters'); + props.onChange({ unit: 'meters', value: qty.scalar }); + } catch (error) { + console.error('Conversion error', error); + } + } + }; + + const handleUnitChange = (newUnit: string) => { + if (!newUnit) return; + setUnit(newUnit); + try { + const convertedValue = Qty(inputValue, unit).to(newUnit).scalar; + setInputValue(convertedValue); + } catch (error) { + console.error('Unit conversion error', error); + } + }; + + return ( + + + + + + { + handleUnitChange(unit); + }} + renderInput={(params) => ( + + )} + /> + + + ); +} diff --git a/src/datatables/data/wire_gauge.ts b/src/datatables/data/wire_gauge.ts index 94b2a19..52e4536 100644 --- a/src/datatables/data/wire_gauge.ts +++ b/src/datatables/data/wire_gauge.ts @@ -69,7 +69,7 @@ const data: DataTable = { }; for (const key in data.data) { - data.data[key].area = Math.PI * data.data[key].diameter ** 2; + data.data[key].area = Math.PI * (data.data[key].diameter / 2) ** 2; } export default data; diff --git a/src/pages/tools/number/generic-calc/data/types.ts b/src/pages/tools/number/generic-calc/data/types.ts index 084413b..52d6a57 100644 --- a/src/pages/tools/number/generic-calc/data/types.ts +++ b/src/pages/tools/number/generic-calc/data/types.ts @@ -2,6 +2,12 @@ export interface GenericCalcType { title: string; name: string; formula: string; + + extraOutputs?: { + title: string; + formula: string; + unit: string; + }[]; selections?: { title: string; source: string; diff --git a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts index f0338a2..59deba8 100644 --- a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts +++ b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts @@ -2,7 +2,7 @@ import type { GenericCalcType } from './types'; const voltagedropinwire: GenericCalcType = { title: 'Round trip voltage drop in cable', name: 'cable-voltage-drop', - formula: 'x = (((p * L) / (A/10**6) ) *2) * I**2', + formula: 'x = (((p * L) / (A/10**6) ) *2) * I', selections: [ { title: 'Material', @@ -22,6 +22,19 @@ const voltagedropinwire: GenericCalcType = { } } ], + + extraOutputs: [ + { + title: 'Total Resistance', + formula: '((p * L) / (A/10**6))*2', + unit: 'Ω' + }, + { + title: 'Total Power Dissipated', + formula: 'I**2 * (((p * L) / (A/10**6))*2)', + unit: 'W' + } + ], variables: [ { name: 'L', diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 473c85d..207a237 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -49,6 +49,10 @@ export default async function makeTool( [key: string]: string; }>({}); + const [extraOutputs, setExtraOutputs] = useState<{ + [key: string]: string; + }>({}); + const updateVarField = ( name: string, value: number, @@ -273,6 +277,15 @@ export default async function makeTool( ))} + + {calcData.extraOutputs?.map((extraOutput) => ( + + {extraOutput.title} + {extraOutputs[extraOutput.title]} + {extraOutput.unit} + + + ))}
) @@ -311,6 +324,25 @@ export default async function makeTool( } else { setShortResult(''); } + + if (calcData.extraOutputs !== undefined) { + for (let i = 0; i < calcData.extraOutputs.length; i++) { + const extraOutput = calcData.extraOutputs[i]; + + let expr = nerdamer(extraOutput.formula); + + Object.keys(values.vars).forEach((key) => { + if (key === values.outputVariable) return; + expr = expr.sub(key, values.vars[key].value.toString()); + }); + + const result: nerdamer.Expression = expr.evaluate(); + + if (result) { + extraOutputs[extraOutput.title] = result.toDecimal(); + } + } + } }} /> ); From 6b27595a898ded2e3962d6c84b81da6ab927bf3a Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Wed, 2 Apr 2025 19:42:13 -0600 Subject: [PATCH 05/53] Initial support for units --- src/components/input/NumericInputWithUnit.tsx | 37 +++++++++++++------ src/pages/tools/number/generic-calc/index.tsx | 32 +++++++++------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index bef047b..690fa2b 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -1,11 +1,14 @@ import React, { useState, useEffect } from 'react'; -import { TextField, Grid } from '@mui/material'; +import { Grid, TextField } from '@mui/material'; +import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import Autocomplete from '@mui/material/Autocomplete'; import Qty from 'js-quantities'; +import { parse } from 'path'; +import { b } from 'vitest/dist/suite-IbNSsUWN.js'; export default function NumericInputWithUnit(props: { value: { value: number; unit: string }; - onChange: (value: { value: number; unit: string }) => void; + onOwnChange: (value: { value: number; unit: string }, ...baseProps) => void; }) { const [inputValue, setInputValue] = useState(props.value.value); const [unit, setUnit] = useState(props.value.unit); @@ -26,13 +29,13 @@ export default function NumericInputWithUnit(props: { setUnit(props.value.unit); }, [props.value]); - const handleValueChange = (event: React.ChangeEvent) => { - const newValue = parseFloat(event.target.value) || 0; + const handleValueChange = (val: { value: number; unit: string }) => { + const newValue = val.value; setInputValue(newValue); - if (props.onChange) { + if (props.onOwnChange) { try { - const qty = Qty(newValue, unit).to('meters'); - props.onChange({ unit: 'meters', value: qty.scalar }); + const qty = Qty(newValue, unit).to(val.unit); + props.onOwnChange({ unit: val.unit, value: qty.scalar }); } catch (error) { console.error('Conversion error', error); } @@ -48,16 +51,28 @@ export default function NumericInputWithUnit(props: { } catch (error) { console.error('Unit conversion error', error); } + + if (props.onOwnChange) { + try { + const qty = Qty(inputValue, unit).to(newUnit); + props.onOwnChange({ unit: newUnit, value: qty.scalar }); + } catch (error) { + console.error('Conversion error', error); + } + } }; return ( - + handleValueChange({ value: parseFloat(value), unit: unit }) + } label="Value" /> @@ -65,8 +80,8 @@ export default function NumericInputWithUnit(props: { { - handleUnitChange(unit); + onChange={(event, newValue) => { + handleUnitChange(newValue || ''); }} renderInput={(params) => ( diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 207a237..c1fe85b 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -14,6 +14,7 @@ import ToolContent from '@components/ToolContent'; import { ToolComponentProps } from '@tools/defineTool'; import ToolTextResult from '@components/result/ToolTextResult'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; +import NumericInputWithUnit from '@components/input/NumericInputWithUnit'; import { UpdateField } from '@components/options/ToolOptions'; import { InitialValuesType } from './types'; import type { GenericCalcType } from './data/types'; @@ -42,7 +43,7 @@ export default async function makeTool( return function GenericCalc({ title }: ToolComponentProps) { const [result, setResult] = useState(''); - const [shortResult, setShortResult] = useState(''); + const [shortResult, setShortResult] = useState(''); // For UX purposes we need to track what vars are const [valsBoundToPreset, setValsBoundToPreset] = useState<{ @@ -56,6 +57,7 @@ export default async function makeTool( const updateVarField = ( name: string, value: number, + unit: string, values: InitialValuesType, updateFieldFunc: UpdateField ) => { @@ -63,7 +65,7 @@ export default async function makeTool( const newVars = { ...values.vars }; newVars[name] = { value, - unit: values.vars[name]?.unit || '' + unit: unit }; updateFieldFunc('vars', newVars); }; @@ -111,6 +113,9 @@ export default async function makeTool( dataTableLookup(dataTables[selectionData.source], preset)[ selectionData.bind[key] ], + + dataTables[selectionData.source].columns[selectionData.bind[key]] + ?.unit || '', currentValues, updateFieldFunc ); @@ -225,7 +230,6 @@ export default async function makeTool( Variable Value - Unit Solve For @@ -234,15 +238,17 @@ export default async function makeTool( {variable.title} - updateVarField( variable.name, - parseFloat(val), + parseFloat(val.value), + val.unit, values, updateField ) @@ -258,7 +265,6 @@ export default async function makeTool( type="number" /> - {variable.unit} Date: Thu, 3 Apr 2025 05:32:47 -0600 Subject: [PATCH 06/53] Now with unit conversions --- src/components/input/NumericInputWithUnit.tsx | 75 +++++++++++++++++-- .../tools/number/generic-calc/data/types.ts | 4 +- .../generic-calc/data/wire_voltage_drop.ts | 3 +- src/pages/tools/number/generic-calc/index.tsx | 21 ++++-- 4 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index 690fa2b..34077ee 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -1,16 +1,29 @@ import React, { useState, useEffect } from 'react'; -import { Grid, TextField } from '@mui/material'; +import { Grid, TextField, Select, MenuItem } from '@mui/material'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import Autocomplete from '@mui/material/Autocomplete'; import Qty from 'js-quantities'; -import { parse } from 'path'; -import { b } from 'vitest/dist/suite-IbNSsUWN.js'; +// + +const siPrefixes: { [key: string]: number } = { + '': 1, + k: 1000, + M: 1000000, + G: 1000000000, + T: 1000000000000, + m: 0.001, + u: 0.000001, + n: 0.000000001, + p: 0.000000000001 +}; export default function NumericInputWithUnit(props: { value: { value: number; unit: string }; onOwnChange: (value: { value: number; unit: string }, ...baseProps) => void; + defaultPrefix?: string; }) { const [inputValue, setInputValue] = useState(props.value.value); + const [prefix, setPrefix] = useState(props.defaultPrefix || ''); const [unit, setUnit] = useState(props.value.unit); const [unitOptions, setUnitOptions] = useState([]); @@ -34,7 +47,7 @@ export default function NumericInputWithUnit(props: { setInputValue(newValue); if (props.onOwnChange) { try { - const qty = Qty(newValue, unit).to(val.unit); + const qty = Qty(newValue * siPrefixes[prefix], unit).to(val.unit); props.onOwnChange({ unit: val.unit, value: qty.scalar }); } catch (error) { console.error('Conversion error', error); @@ -42,11 +55,31 @@ export default function NumericInputWithUnit(props: { } }; + const handlePrefixChange = (newPrefix: string) => { + const oldPrefixValue = siPrefixes[prefix]; + const newPrefixValue = siPrefixes[newPrefix]; + + setPrefix(newPrefix); + + // Value does not change, it is just re-formatted for display + // handleValueChange({ + // value: (inputValue * oldPrefixValue) / newPrefixValue, + // unit: unit + // }); + }; + const handleUnitChange = (newUnit: string) => { if (!newUnit) return; + const oldInputValue = inputValue; + const oldUnit = unit; setUnit(newUnit); + setPrefix(''); + try { - const convertedValue = Qty(inputValue, unit).to(newUnit).scalar; + const convertedValue = Qty( + oldInputValue * siPrefixes[prefix], + oldUnit + ).to(newUnit).scalar; setInputValue(convertedValue); } catch (error) { console.error('Unit conversion error', error); @@ -63,20 +96,46 @@ export default function NumericInputWithUnit(props: { }; return ( - + handleValueChange({ value: parseFloat(value), unit: unit }) } label="Value" /> - + + + + + + ({}); const [extraOutputs, setExtraOutputs] = useState<{ - [key: string]: string; + [key: string]: number; }>({}); const updateVarField = ( @@ -240,8 +239,8 @@ export default async function makeTool( ( {extraOutput.title} - {extraOutputs[extraOutput.title]} + + + {extraOutput.unit} @@ -345,7 +354,9 @@ export default async function makeTool( const result: nerdamer.Expression = expr.evaluate(); if (result) { - extraOutputs[extraOutput.title] = result.toDecimal(); + extraOutputs[extraOutput.title] = parseFloat( + result.toDecimal() + ); } } } From 7726adf8a35b9ab879381147ad84bd9b7e4ff270 Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Thu, 3 Apr 2025 05:48:01 -0600 Subject: [PATCH 07/53] More units UI work --- src/components/input/NumericInputWithUnit.tsx | 34 +++++++++++++------ .../data/material_electrical_properties.ts | 14 ++++---- src/datatables/data/wire_gauge.ts | 24 ++++++------- .../generic-calc/data/wire_voltage_drop.ts | 2 +- src/pages/tools/number/generic-calc/index.tsx | 4 +++ 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index 34077ee..9e6f97d 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -19,6 +19,8 @@ const siPrefixes: { [key: string]: number } = { export default function NumericInputWithUnit(props: { value: { value: number; unit: string }; + disabled?: boolean; + disableChangingUnit?: boolean; onOwnChange: (value: { value: number; unit: string }, ...baseProps) => void; defaultPrefix?: string; }) { @@ -31,6 +33,10 @@ export default function NumericInputWithUnit(props: { try { const kind = Qty(props.value.unit).kind(); const units = Qty.getUnits(kind); + if (!units.includes(props.value.unit)) { + units.push(props.value.unit); + } + setUnitOptions(units); } catch (error) { console.error('Invalid unit kind', error); @@ -102,9 +108,10 @@ export default function NumericInputWithUnit(props: { alignItems="center" style={{ minWidth: '20rem' }} > - + - - + ); diff --git a/src/datatables/data/material_electrical_properties.ts b/src/datatables/data/material_electrical_properties.ts index 912d016..246bfd9 100644 --- a/src/datatables/data/material_electrical_properties.ts +++ b/src/datatables/data/material_electrical_properties.ts @@ -1,14 +1,12 @@ export default { title: 'Material Electrical Properties', - columns: [ - { - resistivity_20c: { - title: 'Resistivity at 20°C', - type: 'number', - unit: 'Ω/m' - } + columns: { + resistivity_20c: { + title: 'Resistivity at 20°C', + type: 'number', + unit: 'Ω/m' } - ], + }, data: { Copper: { resistivity_20c: 1.68e-8 diff --git a/src/datatables/data/wire_gauge.ts b/src/datatables/data/wire_gauge.ts index 52e4536..13bbfa7 100644 --- a/src/datatables/data/wire_gauge.ts +++ b/src/datatables/data/wire_gauge.ts @@ -2,20 +2,18 @@ import type { DataTable } from '../types'; const data: DataTable = { title: 'American Wire Gauge', - columns: [ - { - diameter: { - title: 'Diameter', - type: 'number', - unit: 'mm' - }, - area: { - title: 'Area', - type: 'number', - unit: 'mm^2' - } + columns: { + diameter: { + title: 'Diameter', + type: 'number', + unit: 'mm' + }, + area: { + title: 'Area', + type: 'number', + unit: 'mm2' } - ], + }, data: { '0000 AWG': { diameter: 11.684 }, '000 AWG': { diameter: 10.405 }, diff --git a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts index fabe807..2609b02 100644 --- a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts +++ b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts @@ -45,7 +45,7 @@ const voltagedropinwire: GenericCalcType = { { name: 'A', title: 'Wire Area', - unit: 'mm', + unit: 'mm2', default: 1 }, diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 707d04a..1a559e2 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -252,6 +252,10 @@ export default async function makeTool( values.outputVariable === variable.name || valsBoundToPreset[variable.name] !== undefined } + disableChangingUnit={ + values.outputVariable === variable.name || + valsBoundToPreset[variable.name] !== undefined + } onOwnChange={(val) => updateVarField( variable.name, From 7386e670ba20846f5486379709f8d1baf02b58c8 Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Thu, 3 Apr 2025 06:05:26 -0600 Subject: [PATCH 08/53] Fix input disabled status not being reactive --- src/components/input/NumericInputWithUnit.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index 9e6f97d..4a5990f 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -29,6 +29,15 @@ export default function NumericInputWithUnit(props: { const [unit, setUnit] = useState(props.value.unit); const [unitOptions, setUnitOptions] = useState([]); + const [disabled, setDisabled] = useState(props.disabled); + const [disableChangingUnit, setDisableChangingUnit] = useState( + props.disableChangingUnit + ); + + useEffect(() => { + setDisabled(props.disabled); + setDisableChangingUnit(props.disableChangingUnit); + }, [props.disabled, props.disableChangingUnit]); useEffect(() => { try { const kind = Qty(props.value.unit).kind(); @@ -111,7 +120,7 @@ export default function NumericInputWithUnit(props: { Date: Thu, 3 Apr 2025 06:11:21 -0600 Subject: [PATCH 09/53] Descriptions and icons --- src/pages/tools/number/generic-calc/data/ohms_law.ts | 1 + src/pages/tools/number/generic-calc/data/types.ts | 1 + src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts | 2 ++ src/pages/tools/number/generic-calc/meta.ts | 4 ++-- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/tools/number/generic-calc/data/ohms_law.ts b/src/pages/tools/number/generic-calc/data/ohms_law.ts index 25bbc66..28b458f 100644 --- a/src/pages/tools/number/generic-calc/data/ohms_law.ts +++ b/src/pages/tools/number/generic-calc/data/ohms_law.ts @@ -3,6 +3,7 @@ import type { GenericCalcType } from './types'; const ohmsLawCalc: GenericCalcType = { title: "Ohm's Law", name: 'ohms-law', + description: 'Calculates voltage, current and resistance', formula: 'V = I * R', selections: [], variables: [ diff --git a/src/pages/tools/number/generic-calc/data/types.ts b/src/pages/tools/number/generic-calc/data/types.ts index 3be9b75..25ded86 100644 --- a/src/pages/tools/number/generic-calc/data/types.ts +++ b/src/pages/tools/number/generic-calc/data/types.ts @@ -2,6 +2,7 @@ export interface GenericCalcType { title: string; name: string; formula: string; + description?: string; extraOutputs?: { title: string; diff --git a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts index 2609b02..9dcf9fe 100644 --- a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts +++ b/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts @@ -3,6 +3,8 @@ const voltagedropinwire: GenericCalcType = { title: 'Round trip voltage drop in cable', name: 'cable-voltage-drop', formula: 'x = (((p * L) / (A/10**6) ) *2) * I', + description: + 'Calculates round trip voltage and power loss in a 2 conductor cable', selections: [ { title: 'Material', diff --git a/src/pages/tools/number/generic-calc/meta.ts b/src/pages/tools/number/generic-calc/meta.ts index 3254deb..3a23c13 100644 --- a/src/pages/tools/number/generic-calc/meta.ts +++ b/src/pages/tools/number/generic-calc/meta.ts @@ -19,8 +19,8 @@ allGenericCalcs.forEach((x) => { defineTool('number', { name: x.title, path: 'generic-calc/' + x.name, - icon: '', - description: '', + icon: 'lsicon:number-filled', + description: x.description || '', shortDescription: '', keywords: ['generic', 'calc'], longDescription: '', From 9a55c934fabcddba1c28be86a49b996639fa1c15 Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Thu, 3 Apr 2025 21:05:00 -0600 Subject: [PATCH 10/53] Use nerdamer-prime --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfa3e2d..9ca4ea1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "lodash": "^4.17.21", "mime": "^4.0.6", "morsee": "^1.0.9", - "nerdamer": "^1.1.13", + "nerdamer-prime": "^1.2.4", "notistack": "^3.0.1", "omggif": "^1.0.10", "pdf-lib": "^1.17.1", @@ -7833,10 +7833,10 @@ "is-buffer": "^1.0.2" } }, - "node_modules/nerdamer": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/nerdamer/-/nerdamer-1.1.13.tgz", - "integrity": "sha512-kQGQYd42eQpKDOnU8ZnRKF47c+gK6jVC46eUchrABsovtFruHvsjyjBO32jck8QnpZE5z5R8HQw72hQX9Oq2MQ==", + "node_modules/nerdamer-prime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/nerdamer-prime/-/nerdamer-prime-1.2.4.tgz", + "integrity": "sha512-oaGnI7GUTj3T2k2PkeGf/694uLY9pYwFSOkn/5aTuz1RXdJeD6hN0+2csKagB65H6R8J1Zb7UlPq7zEzQ2dumw==", "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index b8dd4f8..745e90c 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "lodash": "^4.17.21", "mime": "^4.0.6", "morsee": "^1.0.9", - "nerdamer": "^1.1.13", + "nerdamer-prime": "^1.2.4", "notistack": "^3.0.1", "omggif": "^1.0.10", "pdf-lib": "^1.17.1", From 9fe703e0f7a3ac7f6c730709859c1fce1becc968 Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Fri, 4 Apr 2025 00:53:21 -0600 Subject: [PATCH 11/53] Definitely still WIP, this lets you group calc inputs into equivalents --- src/components/input/NumericInputWithUnit.tsx | 99 +++---- .../number/generic-calc/data/area_volume.ts | 50 ++++ .../tools/number/generic-calc/data/index.ts | 3 +- .../tools/number/generic-calc/data/types.ts | 15 ++ src/pages/tools/number/generic-calc/index.tsx | 247 ++++++++++++++---- 5 files changed, 312 insertions(+), 102 deletions(-) create mode 100644 src/pages/tools/number/generic-calc/data/area_volume.ts diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index 4a5990f..eb9eeed 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -1,8 +1,10 @@ import React, { useState, useEffect } from 'react'; import { Grid, TextField, Select, MenuItem } from '@mui/material'; +import { NumberField } from '@base-ui-components/react/number-field'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import Autocomplete from '@mui/material/Autocomplete'; import Qty from 'js-quantities'; +import { isNull, set } from 'lodash'; // const siPrefixes: { [key: string]: number } = { @@ -21,12 +23,18 @@ export default function NumericInputWithUnit(props: { value: { value: number; unit: string }; disabled?: boolean; disableChangingUnit?: boolean; - onOwnChange: (value: { value: number; unit: string }, ...baseProps) => void; + onOwnChange: (value: { value: number; unit: string }) => void; defaultPrefix?: string; }) { const [inputValue, setInputValue] = useState(props.value.value); const [prefix, setPrefix] = useState(props.defaultPrefix || ''); - const [unit, setUnit] = useState(props.value.unit); + + // internal display unit + const [unit, setUnit] = useState(''); + + // Whether user has overridden the unit + const [userSelectedUnit, setUserSelectedUnit] = useState(false); + const [unitKind, setUnitKind] = useState(''); const [unitOptions, setUnitOptions] = useState([]); const [disabled, setDisabled] = useState(props.disabled); @@ -38,32 +46,48 @@ export default function NumericInputWithUnit(props: { setDisabled(props.disabled); setDisableChangingUnit(props.disableChangingUnit); }, [props.disabled, props.disableChangingUnit]); + useEffect(() => { - try { + if (unitKind != Qty(props.value.unit).kind()) { + // Update the options for what units similar to this one are available const kind = Qty(props.value.unit).kind(); const units = Qty.getUnits(kind); if (!units.includes(props.value.unit)) { units.push(props.value.unit); } - setUnitOptions(units); - } catch (error) { - console.error('Invalid unit kind', error); + setInputValue(props.value.value); + setUnit(props.value.unit); + setUnitKind(kind); + setUserSelectedUnit(false); + return; } - }, [props.value.unit]); - useEffect(() => { - setInputValue(props.value.value); - setUnit(props.value.unit); - }, [props.value]); + if (userSelectedUnit) { + if (!isNaN(props.value.value)) { + const converted = Qty(props.value.value, props.value.unit).to( + unit + ).scalar; + setInputValue(converted); + } else { + setInputValue(props.value.value); + } + } else { + setInputValue(props.value.value); + setUnit(props.value.unit); + } + }, [props.value.value, props.value.unit, unit]); - const handleValueChange = (val: { value: number; unit: string }) => { - const newValue = val.value; + const handleUserValueChange = (newValue: number) => { setInputValue(newValue); + if (props.onOwnChange) { try { - const qty = Qty(newValue * siPrefixes[prefix], unit).to(val.unit); - props.onOwnChange({ unit: val.unit, value: qty.scalar }); + const converted = Qty(newValue * siPrefixes[prefix], unit).to( + props.value.unit + ).scalar; + + props.onOwnChange({ unit: props.value.unit, value: converted }); } catch (error) { console.error('Conversion error', error); } @@ -75,39 +99,19 @@ export default function NumericInputWithUnit(props: { const newPrefixValue = siPrefixes[newPrefix]; setPrefix(newPrefix); - - // Value does not change, it is just re-formatted for display - // handleValueChange({ - // value: (inputValue * oldPrefixValue) / newPrefixValue, - // unit: unit - // }); }; - const handleUnitChange = (newUnit: string) => { + const handleUserUnitChange = (newUnit: string) => { if (!newUnit) return; const oldInputValue = inputValue; const oldUnit = unit; setUnit(newUnit); setPrefix(''); - try { - const convertedValue = Qty( - oldInputValue * siPrefixes[prefix], - oldUnit - ).to(newUnit).scalar; - setInputValue(convertedValue); - } catch (error) { - console.error('Unit conversion error', error); - } - - if (props.onOwnChange) { - try { - const qty = Qty(inputValue, unit).to(newUnit); - props.onOwnChange({ unit: newUnit, value: qty.scalar }); - } catch (error) { - console.error('Conversion error', error); - } - } + const convertedValue = Qty(oldInputValue * siPrefixes[prefix], oldUnit).to( + newUnit + ).scalar; + setInputValue(convertedValue); }; return ( @@ -119,17 +123,13 @@ export default function NumericInputWithUnit(props: { > - handleValueChange({ value: parseFloat(value), unit: unit }) - } - label="Value" + onOwnChange={(value) => handleUserValueChange(parseFloat(value))} /> @@ -140,8 +140,8 @@ export default function NumericInputWithUnit(props: { label="Prefix" title="Prefix" value={prefix} - onChange={(event, newValue) => { - handlePrefixChange(newValue?.props?.value || ''); + onChange={(evt) => { + handlePrefixChange(evt.target.value || ''); }} > {Object.keys(siPrefixes).map((key) => ( @@ -159,8 +159,9 @@ export default function NumericInputWithUnit(props: { label="Unit" title="Unit" value={unit} - onChange={(event, newValue) => { - handleUnitChange(newValue?.props?.value || ''); + onChange={(event) => { + setUserSelectedUnit(true); + handleUserUnitChange(event.target.value || ''); }} > {unitOptions.map((key) => ( diff --git a/src/pages/tools/number/generic-calc/data/area_volume.ts b/src/pages/tools/number/generic-calc/data/area_volume.ts new file mode 100644 index 0000000..487b80a --- /dev/null +++ b/src/pages/tools/number/generic-calc/data/area_volume.ts @@ -0,0 +1,50 @@ +import type { GenericCalcType } from './types'; + +export const areaSphere: GenericCalcType = { + title: 'Area of a Sphere', + name: 'area-sphere', + description: 'Area of a Sphere', + formula: 'A = 4 * pi * r**2', + selections: [], + variables: [ + { + name: 'A', + title: 'Area', + unit: 'mm2' + }, + { + name: 'r', + title: 'Radius', + unit: 'mm', + default: 1 + } + ] +}; + +export const volumeSphere: GenericCalcType = { + title: 'Volume of a Sphere', + name: 'volume-sphere', + description: 'Volume of a Sphere', + formula: 'v = (4/3) * pi * r**3', + selections: [], + variables: [ + { + name: 'v', + title: 'Volume', + unit: 'mm3' + }, + { + name: 'r', + title: 'Radius', + unit: 'mm', + default: 1, + alternates: [ + { + title: 'Diameter', + formula: 'x = 2 * v', + unit: 'mm' + } + ] + } + ] +}; diff --git a/src/pages/tools/number/generic-calc/data/index.ts b/src/pages/tools/number/generic-calc/data/index.ts index 9249a30..3b27f80 100644 --- a/src/pages/tools/number/generic-calc/data/index.ts +++ b/src/pages/tools/number/generic-calc/data/index.ts @@ -1,4 +1,5 @@ import ohmslaw from './ohms_law'; import voltagedropinwire from './wire_voltage_drop'; +import { areaSphere, volumeSphere } from './area_volume'; -export default [ohmslaw, voltagedropinwire]; +export default [ohmslaw, voltagedropinwire, areaSphere, volumeSphere]; diff --git a/src/pages/tools/number/generic-calc/data/types.ts b/src/pages/tools/number/generic-calc/data/types.ts index 25ded86..9742e5b 100644 --- a/src/pages/tools/number/generic-calc/data/types.ts +++ b/src/pages/tools/number/generic-calc/data/types.ts @@ -1,3 +1,9 @@ +export interface AlternativeVarInfo { + title: string; + unit: string; + defaultPrefix?: string; + formula: string; +} export interface GenericCalcType { title: string; name: string; @@ -26,5 +32,14 @@ export interface GenericCalcType { defaultPrefix?: string; // If absence, assume it's the default target var default?: number; + + // If present and false, don't allow user to select this as output + solvable?: boolean; + + // Alternates are alternate ways of entering the exact same thing, + // like the diameter or radius. The formula for an alternate + // can use only one variable, always called v, which is the main + // variable it's an alternate of + alternates?: AlternativeVarInfo[]; }[]; } diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 1a559e2..c732114 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -16,14 +16,38 @@ import ToolTextResult from '@components/result/ToolTextResult'; import NumericInputWithUnit from '@components/input/NumericInputWithUnit'; import { UpdateField } from '@components/options/ToolOptions'; import { InitialValuesType } from './types'; -import type { GenericCalcType } from './data/types'; +import type { AlternativeVarInfo, GenericCalcType } from './data/types'; import type { DataTable } from 'datatables'; import { getDataTable, dataTableLookup } from 'datatables'; -import nerdamer from 'nerdamer'; -import 'nerdamer/Algebra'; -import 'nerdamer/Solve'; -import 'nerdamer/Calculus'; +import nerdamer from 'nerdamer-prime'; +import 'nerdamer-prime/Algebra'; +import 'nerdamer-prime/Solve'; +import 'nerdamer-prime/Calculus'; +import Qty from 'js-quantities'; + +function numericSolveEquationFor( + equation: string, + varName: string, + variables: { [key: string]: number } +) { + let expr = nerdamer(equation); + for (const key in variables) { + expr = expr.sub(key, variables[key].toString()); + } + + let result: nerdamer.Expression | nerdamer.Expression[] = + expr.solveFor(varName); + + // Sometimes the result is an array, check for it while keeping linter happy + if ((result as unknown as nerdamer.Expression).toDecimal === undefined) { + result = (result as unknown as nerdamer.Expression[])[0]; + } + + return parseFloat( + (result as unknown as nerdamer.Expression).evaluate().toDecimal() + ); +} export default async function makeTool( calcData: GenericCalcType @@ -42,7 +66,15 @@ export default async function makeTool( return function GenericCalc({ title }: ToolComponentProps) { const [result, setResult] = useState(''); - const [shortResult, setShortResult] = useState(''); + + const [alternatesByVariable, setAlternatesByVariable] = useState<{ + [key: string]: { + value: { + value: number; + unit: string; + }; + }[]; + }>({}); // For UX purposes we need to track what vars are const [valsBoundToPreset, setValsBoundToPreset] = useState<{ @@ -128,6 +160,9 @@ export default async function makeTool( }; calcData.variables.forEach((variable) => { + if (variable.solvable === undefined) { + variable.solvable = true; + } if (variable.default === undefined) { initialValues.vars[variable.name] = { value: NaN, @@ -160,18 +195,72 @@ export default async function makeTool( } }); + function getAlternate( + alternateInfo: AlternativeVarInfo, + mainInfo: GenericCalcType['variables'][number], + mainValue: { + value: number; + unit: string; + } + ) { + if (isNaN(mainValue.value)) return NaN; + const canonicalValue = Qty(mainValue.value, mainValue.unit).to( + mainInfo.unit + ).scalar; + + return numericSolveEquationFor(alternateInfo.formula, 'x', { + v: canonicalValue + }); + } + + function getMainFromAlternate( + alternateInfo: AlternativeVarInfo, + mainInfo: GenericCalcType['variables'][number], + alternateValue: { + value: number; + unit: string; + } + ) { + if (isNaN(alternateValue.value)) return NaN; + const canonicalValue = Qty(alternateValue.value, alternateValue.unit).to( + alternateInfo.unit + ).scalar; + + return numericSolveEquationFor(alternateInfo.formula, 'v', { + x: canonicalValue + }); + } + + calcData.variables.forEach((variable) => { + if (variable.alternates) { + variable.alternates.forEach((alt) => { + const altValue = getAlternate( + alt, + variable, + initialValues.vars[variable.name] + ); + if (alternatesByVariable[variable.name] === undefined) { + alternatesByVariable[variable.name] = []; + } + + alternatesByVariable[variable.name].push({ + value: { value: altValue, unit: variable.unit } + }); + }); + } + }); + return ( - } initialValues={initialValues} toolInfo={{ - title: 'Common Equations', + title: calcData.title, description: - 'Common mathematical equations that can be used in calculations.' + (calcData.description || '') + + ' Generated from formula: ' + + calcData.formula }} getGroups={({ values, updateField }) => [ { @@ -227,7 +316,6 @@ export default async function makeTool( - Variable Value Solve For @@ -235,38 +323,80 @@ export default async function makeTool( {calcData.variables.map((variable) => ( - {variable.title} - - updateVarField( - variable.name, - parseFloat(val.value), - val.unit, - values, - updateField - ) - } - type="number" - /> +
+ + {variable.title} + + + updateVarField( + variable.name, + val.value, + val.unit, + values, + updateField + ) + } + type="number" + /> + + + + {variable.alternates?.map((alt) => ( + + {alt.title} + + + updateVarField( + variable.name, + getMainFromAlternate(alt, variable, val), + variable.unit, + values, + updateField + ) + } + > + + + ))} +
@@ -274,7 +404,8 @@ export default async function makeTool( value={variable.name} checked={values.outputVariable === variable.name} disabled={ - valsBoundToPreset[variable.name] !== undefined + valsBoundToPreset[variable.name] !== undefined || + variable.solvable === false } onClick={() => handleSelectedTargetChange( @@ -292,7 +423,6 @@ export default async function makeTool( {extraOutput.title} Date: Fri, 4 Apr 2025 00:55:18 -0600 Subject: [PATCH 12/53] Remove unused stuff --- src/components/input/NumericInputWithUnit.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index eb9eeed..c895674 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -1,10 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { Grid, TextField, Select, MenuItem } from '@mui/material'; -import { NumberField } from '@base-ui-components/react/number-field'; +import { Grid, Select, MenuItem } from '@mui/material'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; -import Autocomplete from '@mui/material/Autocomplete'; import Qty from 'js-quantities'; -import { isNull, set } from 'lodash'; // const siPrefixes: { [key: string]: number } = { From 32adf8effeb746feadacfbbcbea7b5a2c6351765 Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Fri, 4 Apr 2025 00:58:04 -0600 Subject: [PATCH 13/53] UI for the extra outputs --- src/pages/tools/number/generic-calc/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index c732114..caf99d2 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -420,8 +420,8 @@ export default async function makeTool( {calcData.extraOutputs?.map((extraOutput) => ( - {extraOutput.title} + {extraOutput.title} - {extraOutput.unit} ))} From 1a9ed879d6b33a26c340dca790641100ca78f70f Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Fri, 4 Apr 2025 01:11:50 -0600 Subject: [PATCH 14/53] Allow manually derived formulas for things nerdimer can't handl --- .../number/generic-calc/data/area_volume.ts | 1 + .../tools/number/generic-calc/data/types.ts | 4 ++++ src/pages/tools/number/generic-calc/index.tsx | 22 ++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/pages/tools/number/generic-calc/data/area_volume.ts b/src/pages/tools/number/generic-calc/data/area_volume.ts index 487b80a..8d71fa1 100644 --- a/src/pages/tools/number/generic-calc/data/area_volume.ts +++ b/src/pages/tools/number/generic-calc/data/area_volume.ts @@ -15,6 +15,7 @@ export const areaSphere: GenericCalcType = { { name: 'r', title: 'Radius', + formula: 'r = sqrt(A/pi) / 2', unit: 'mm', default: 1 } diff --git a/src/pages/tools/number/generic-calc/data/types.ts b/src/pages/tools/number/generic-calc/data/types.ts index 9742e5b..45a3d1a 100644 --- a/src/pages/tools/number/generic-calc/data/types.ts +++ b/src/pages/tools/number/generic-calc/data/types.ts @@ -36,6 +36,10 @@ export interface GenericCalcType { // If present and false, don't allow user to select this as output solvable?: boolean; + // Alternate rearrangement of the formula, to be used when calculating this. + // If missing, the main formula is used with auto derivation. + formula?: string; + // Alternates are alternate ways of entering the exact same thing, // like the diameter or radius. The formula for an alternate // can use only one variable, always called v, which is the main diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index caf99d2..9c51797 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -25,6 +25,7 @@ import 'nerdamer-prime/Algebra'; import 'nerdamer-prime/Solve'; import 'nerdamer-prime/Calculus'; import Qty from 'js-quantities'; +import { error } from 'console'; function numericSolveEquationFor( equation: string, @@ -444,10 +445,29 @@ export default async function makeTool( setResult('Please select a solve for variable'); return; } - let expr = nerdamer(calcData.formula); + let expr: nerdamer.Expression | null = null; + + for (const i of calcData.variables) { + if (i.name === values.outputVariable) { + if (i.formula !== undefined) { + expr = nerdamer(i.formula); + } + } + } + + if (expr == null) { + expr = nerdamer(calcData.formula); + } + if (expr == null) { + throw new Error('No formula found'); + return; + } Object.keys(values.vars).forEach((key) => { if (key === values.outputVariable) return; + if (expr === null) { + throw new Error('Math fail'); + } expr = expr.sub(key, values.vars[key].value.toString()); }); From 3014443163abda940aa0680b79c899ab3c63a9d0 Mon Sep 17 00:00:00 2001 From: Daniel Dunn Date: Fri, 4 Apr 2025 01:22:16 -0600 Subject: [PATCH 15/53] More area units --- src/components/input/NumericInputWithUnit.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index c895674..daf00bc 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -52,6 +52,15 @@ export default function NumericInputWithUnit(props: { if (!units.includes(props.value.unit)) { units.push(props.value.unit); } + + // Workaround because the lib doesn't list them + if (kind == 'area') { + units.push('km^2'); + units.push('mile^2'); + units.push('inch^2'); + units.push('m^2'); + units.push('cm^2'); + } setUnitOptions(units); setInputValue(props.value.value); setUnit(props.value.unit); From ab503c642d1d28699f1a83090e1bde2addbe535b Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sat, 5 Apr 2025 04:36:22 +0000 Subject: [PATCH 16/53] refactor: init --- .idea/workspace.xml | 141 ++++++++-------- .../omni-tools/src/pages/tools/index.ts | 1 - .../pages/tools/number/calculators/index.ts | 1 - .../src/pages/tools/number/index.ts | 1 - src/components/ToolContent.tsx | 2 +- src/components/ToolInputAndResult.tsx | 24 +-- src/components/input/NumericInputWithUnit.tsx | 2 +- src/datatables/data/index.ts | 10 -- src/datatables/index.ts | 10 -- src/datatables/types.ts | 4 +- .../tools/number/generic-calc/data/index.ts | 9 +- .../data/{ohms_law.ts => ohmsLaw.ts} | 2 +- .../number/generic-calc/data/sphereArea.ts | 25 +++ .../data/{area_volume.ts => sphereVolume.ts} | 28 +--- .../tools/number/generic-calc/data/types.ts | 7 +- ...e_voltage_drop.ts => voltageDropInWire.ts} | 13 +- src/pages/tools/number/generic-calc/index.tsx | 150 ++++++++---------- .../tools/number/generic-calc/service.ts | 5 - 18 files changed, 197 insertions(+), 238 deletions(-) delete mode 100644 home/daniel/Projects/omni-tools/src/pages/tools/index.ts delete mode 100644 home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts delete mode 100644 home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts delete mode 100644 src/datatables/data/index.ts rename src/pages/tools/number/generic-calc/data/{ohms_law.ts => ohmsLaw.ts} (96%) create mode 100644 src/pages/tools/number/generic-calc/data/sphereArea.ts rename src/pages/tools/number/generic-calc/data/{area_volume.ts => sphereVolume.ts} (51%) rename src/pages/tools/number/generic-calc/data/{wire_voltage_drop.ts => voltageDropInWire.ts} (78%) delete mode 100644 src/pages/tools/number/generic-calc/service.ts diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 27386d7..ca23a2a 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,32 +5,25 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - { + "prStates": [ { - "id": { - "id": "PR_kwDOMJIfts51PkS9", - "number": 22 + "id": { + "id": "PR_kwDOMJIfts51PkS9", + "number": 22 }, - "lastSeen": 1741207144695 + "lastSeen": 1741207144695 }, { - "id": { - "id": "PR_kwDOMJIfts6NiNYl", - "number": 32 + "id": { + "id": "PR_kwDOMJIfts6NiNYl", + "number": 32 }, - "lastSeen": 1741209723869 + "lastSeen": 1741209723869 }, { - "id": { - "id": "PR_kwDOMJIfts6Nheyd", - "number": 31 + "id": { + "id": "PR_kwDOMJIfts6Nheyd", + "number": 31 }, - "lastSeen": 1741213371410 + "lastSeen": 1741213371410 }, { - "id": { - "id": "PR_kwDOMJIfts6NmRBs", - "number": 33 + "id": { + "id": "PR_kwDOMJIfts6NmRBs", + "number": 33 }, - "lastSeen": 1741282429036 + "lastSeen": 1741282429036 }, { - "id": { - "id": "PR_kwDOMJIfts5zyFTs", - "number": 15 + "id": { + "id": "PR_kwDOMJIfts5zyFTs", + "number": 15 }, - "lastSeen": 1741535540953 + "lastSeen": 1741535540953 }, { - "id": { - "id": "PR_kwDOMJIfts6QQB3c", - "number": 59 + "id": { + "id": "PR_kwDOMJIfts6QQB3c", + "number": 59 }, - "lastSeen": 1743018960900 + "lastSeen": 1743018960900 }, { - "id": { - "id": "PR_kwDOMJIfts6QMPEg", - "number": 58 + "id": { + "id": "PR_kwDOMJIfts6QMPEg", + "number": 58 }, - "lastSeen": 1743019452983 + "lastSeen": 1743019452983 }, { - "id": { - "id": "PR_kwDOMJIfts6QZvRI", - "number": 61 + "id": { + "id": "PR_kwDOMJIfts6QZvRI", + "number": 61 }, - "lastSeen": 1743103196866 + "lastSeen": 1743103196866 }, { - "id": { - "id": "PR_kwDOMJIfts6QqPrQ", - "number": 73 + "id": { + "id": "PR_kwDOMJIfts6QqPrQ", + "number": 73 }, - "lastSeen": 1743265865001 + "lastSeen": 1743265865001 }, { - "id": { - "id": "PR_kwDOMJIfts6Qp5nI", - "number": 72 + "id": { + "id": "PR_kwDOMJIfts6Qp5nI", + "number": 72 }, - "lastSeen": 1743338472110 + "lastSeen": 1743338472110 }, { - "id": { - "id": "PR_kwDOMJIfts6QsjlS", - "number": 76 + "id": { + "id": "PR_kwDOMJIfts6QsjlS", + "number": 76 }, - "lastSeen": 1743352150953 + "lastSeen": 1743352150953 } ] -}]]> +} { "selectedUrlAndAccountId": { "url": "https://github.com/iib0011/omni-tools.git", @@ -199,7 +192,7 @@ "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", "Vitest.replaceText function.executor": "Run", "Vitest.timeBetweenDates.executor": "Run", - "git-widget-placeholder": "dark-mode", + "git-widget-placeholder": "#89 on generic-calc", "ignore.virus.scanning.warn.message": "true", "kotlin-language-version-configured": "true", "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/@types", diff --git a/home/daniel/Projects/omni-tools/src/pages/tools/index.ts b/home/daniel/Projects/omni-tools/src/pages/tools/index.ts deleted file mode 100644 index 71e8eea..0000000 --- a/home/daniel/Projects/omni-tools/src/pages/tools/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const toolsTools = []; diff --git a/home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts b/home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts deleted file mode 100644 index f7a9c9d..0000000 --- a/home/daniel/Projects/omni-tools/src/pages/tools/number/calculators/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const calculatorsTools = []; diff --git a/home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts b/home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts deleted file mode 100644 index 90c9f0e..0000000 --- a/home/daniel/Projects/omni-tools/src/pages/tools/number/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const numberTools = []; diff --git a/src/components/ToolContent.tsx b/src/components/ToolContent.tsx index 2e3a6b6..721035d 100644 --- a/src/components/ToolContent.tsx +++ b/src/components/ToolContent.tsx @@ -40,7 +40,7 @@ const FormikListenerComponent = ({ interface ToolContentProps extends ToolComponentProps { inputComponent?: ReactNode; - resultComponent: ReactNode; + resultComponent?: ReactNode; renderCustomInput?: ( values: T, setFieldValue: (fieldName: string, value: any) => void diff --git a/src/components/ToolInputAndResult.tsx b/src/components/ToolInputAndResult.tsx index a07721f..390f560 100644 --- a/src/components/ToolInputAndResult.tsx +++ b/src/components/ToolInputAndResult.tsx @@ -6,18 +6,20 @@ export default function ToolInputAndResult({ result }: { input?: ReactNode; - result: ReactNode; + result?: ReactNode; }) { - return ( - - {input && ( - - {input} + if (input || result) { + return ( + + {input && ( + + {input} + + )} + + {result} - )} - - {result} - - ); + ); + } } diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index daf00bc..2a12c8d 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -20,7 +20,7 @@ export default function NumericInputWithUnit(props: { value: { value: number; unit: string }; disabled?: boolean; disableChangingUnit?: boolean; - onOwnChange: (value: { value: number; unit: string }) => void; + onOwnChange?: (value: { value: number; unit: string }) => void; defaultPrefix?: string; }) { const [inputValue, setInputValue] = useState(props.value.value); diff --git a/src/datatables/data/index.ts b/src/datatables/data/index.ts deleted file mode 100644 index c5c5420..0000000 --- a/src/datatables/data/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { DataTable } from '../types'; -import wiregauge from './wire_gauge'; -import material_electrical_properties from './material_electrical_properties'; - -const allDataTables: { [key: string]: DataTable } = { - 'wire-gauge': wiregauge, - 'material-electrical-properties': material_electrical_properties -}; - -export default allDataTables; diff --git a/src/datatables/index.ts b/src/datatables/index.ts index 37174b2..e661d11 100644 --- a/src/datatables/index.ts +++ b/src/datatables/index.ts @@ -1,18 +1,8 @@ import type { DataTable } from './types.ts'; -export async function getDataTable(name: string): Promise { - const allDataTables = (await import('./data/index')).default; - return allDataTables[name]; -} - /* Used in case later we want any kind of computed extra data */ export function dataTableLookup(table: DataTable, key: string): any { return table.data[key]; } -export async function listDataTables(): Promise<{ [name: string]: DataTable }> { - const allDataTables = (await import('./data/index')).default; - return allDataTables; -} - export { DataTable }; diff --git a/src/datatables/types.ts b/src/datatables/types.ts index c2199d4..6a77ccc 100644 --- a/src/datatables/types.ts +++ b/src/datatables/types.ts @@ -1,4 +1,4 @@ -/* +/* Represents a set of rows indexed by a key. Used for calculator presets @@ -7,7 +7,7 @@ export interface DataTable { title: string; /* A JSON schema properties */ columns: { - [key: string]: any; + [key: string]: { title: string; type: string; unit: string }; }; data: { [key: string]: { diff --git a/src/pages/tools/number/generic-calc/data/index.ts b/src/pages/tools/number/generic-calc/data/index.ts index 3b27f80..1a700db 100644 --- a/src/pages/tools/number/generic-calc/data/index.ts +++ b/src/pages/tools/number/generic-calc/data/index.ts @@ -1,5 +1,6 @@ -import ohmslaw from './ohms_law'; -import voltagedropinwire from './wire_voltage_drop'; -import { areaSphere, volumeSphere } from './area_volume'; +import ohmslaw from './ohmsLaw'; +import voltageDropInWire from './voltageDropInWire'; +import sphereArea from './sphereArea'; +import sphereVolume from './sphereVolume'; -export default [ohmslaw, voltagedropinwire, areaSphere, volumeSphere]; +export default [ohmslaw, voltageDropInWire, sphereArea, sphereVolume]; diff --git a/src/pages/tools/number/generic-calc/data/ohms_law.ts b/src/pages/tools/number/generic-calc/data/ohmsLaw.ts similarity index 96% rename from src/pages/tools/number/generic-calc/data/ohms_law.ts rename to src/pages/tools/number/generic-calc/data/ohmsLaw.ts index 28b458f..3065152 100644 --- a/src/pages/tools/number/generic-calc/data/ohms_law.ts +++ b/src/pages/tools/number/generic-calc/data/ohmsLaw.ts @@ -5,7 +5,7 @@ const ohmsLawCalc: GenericCalcType = { name: 'ohms-law', description: 'Calculates voltage, current and resistance', formula: 'V = I * R', - selections: [], + presets: [], variables: [ { name: 'V', diff --git a/src/pages/tools/number/generic-calc/data/sphereArea.ts b/src/pages/tools/number/generic-calc/data/sphereArea.ts new file mode 100644 index 0000000..dd84991 --- /dev/null +++ b/src/pages/tools/number/generic-calc/data/sphereArea.ts @@ -0,0 +1,25 @@ +import type { GenericCalcType } from './types'; + +const areaSphere: GenericCalcType = { + title: 'Area of a Sphere', + name: 'area-sphere', + description: 'Area of a Sphere', + formula: 'A = 4 * pi * r**2', + presets: [], + variables: [ + { + name: 'A', + title: 'Area', + unit: 'mm2' + }, + { + name: 'r', + title: 'Radius', + formula: 'r = sqrt(A/pi) / 2', + unit: 'mm', + default: 1 + } + ] +}; + +export default areaSphere; diff --git a/src/pages/tools/number/generic-calc/data/area_volume.ts b/src/pages/tools/number/generic-calc/data/sphereVolume.ts similarity index 51% rename from src/pages/tools/number/generic-calc/data/area_volume.ts rename to src/pages/tools/number/generic-calc/data/sphereVolume.ts index 8d71fa1..5ec4764 100644 --- a/src/pages/tools/number/generic-calc/data/area_volume.ts +++ b/src/pages/tools/number/generic-calc/data/sphereVolume.ts @@ -1,33 +1,11 @@ import type { GenericCalcType } from './types'; -export const areaSphere: GenericCalcType = { - title: 'Area of a Sphere', - name: 'area-sphere', - description: 'Area of a Sphere', - formula: 'A = 4 * pi * r**2', - selections: [], - variables: [ - { - name: 'A', - title: 'Area', - unit: 'mm2' - }, - { - name: 'r', - title: 'Radius', - formula: 'r = sqrt(A/pi) / 2', - unit: 'mm', - default: 1 - } - ] -}; - -export const volumeSphere: GenericCalcType = { +const volumeSphere: GenericCalcType = { title: 'Volume of a Sphere', name: 'volume-sphere', description: 'Volume of a Sphere', formula: 'v = (4/3) * pi * r**3', - selections: [], + presets: [], variables: [ { name: 'v', @@ -49,3 +27,5 @@ export const volumeSphere: GenericCalcType = { } ] }; + +export default volumeSphere; diff --git a/src/pages/tools/number/generic-calc/data/types.ts b/src/pages/tools/number/generic-calc/data/types.ts index 45a3d1a..1ed2148 100644 --- a/src/pages/tools/number/generic-calc/data/types.ts +++ b/src/pages/tools/number/generic-calc/data/types.ts @@ -1,9 +1,12 @@ +import { DataTable } from '../../../../../datatables'; + export interface AlternativeVarInfo { title: string; unit: string; defaultPrefix?: string; formula: string; } + export interface GenericCalcType { title: string; name: string; @@ -17,9 +20,9 @@ export interface GenericCalcType { // Si prefix default defaultPrefix?: string; }[]; - selections?: { + presets?: { title: string; - source: string; + source: DataTable; default: string; bind: { [key: string]: string; diff --git a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts b/src/pages/tools/number/generic-calc/data/voltageDropInWire.ts similarity index 78% rename from src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts rename to src/pages/tools/number/generic-calc/data/voltageDropInWire.ts index 9dcf9fe..5b2d0aa 100644 --- a/src/pages/tools/number/generic-calc/data/wire_voltage_drop.ts +++ b/src/pages/tools/number/generic-calc/data/voltageDropInWire.ts @@ -1,14 +1,17 @@ import type { GenericCalcType } from './types'; -const voltagedropinwire: GenericCalcType = { +import material_electrical_properties from '../../../../../datatables/data/material_electrical_properties'; +import wire_gauge from '../../../../../datatables/data/wire_gauge'; + +const voltageDropInWire: GenericCalcType = { title: 'Round trip voltage drop in cable', name: 'cable-voltage-drop', formula: 'x = (((p * L) / (A/10**6) ) *2) * I', description: 'Calculates round trip voltage and power loss in a 2 conductor cable', - selections: [ + presets: [ { title: 'Material', - source: 'material-electrical-properties', + source: material_electrical_properties, default: 'Copper', bind: { p: 'resistivity_20c' @@ -17,7 +20,7 @@ const voltagedropinwire: GenericCalcType = { { title: 'Wire Gauge', - source: 'wire-gauge', + source: wire_gauge, default: '24 AWG', bind: { A: 'area' @@ -72,4 +75,4 @@ const voltagedropinwire: GenericCalcType = { ] }; -export default voltagedropinwire; +export default voltageDropInWire; diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 9c51797..3dc21f9 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -1,31 +1,29 @@ import { - Box, Autocomplete, - TextField, + Box, Radio, Table, TableBody, TableCell, TableHead, - TableRow + TableRow, + TextField } from '@mui/material'; -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import ToolContent from '@components/ToolContent'; import { ToolComponentProps } from '@tools/defineTool'; -import ToolTextResult from '@components/result/ToolTextResult'; import NumericInputWithUnit from '@components/input/NumericInputWithUnit'; import { UpdateField } from '@components/options/ToolOptions'; import { InitialValuesType } from './types'; import type { AlternativeVarInfo, GenericCalcType } from './data/types'; -import type { DataTable } from 'datatables'; -import { getDataTable, dataTableLookup } from 'datatables'; +import { dataTableLookup } from 'datatables'; import nerdamer from 'nerdamer-prime'; import 'nerdamer-prime/Algebra'; import 'nerdamer-prime/Solve'; import 'nerdamer-prime/Calculus'; import Qty from 'js-quantities'; -import { error } from 'console'; +import { CustomSnackBarContext } from 'contexts/CustomSnackBarContext'; function numericSolveEquationFor( equation: string, @@ -59,15 +57,8 @@ export default async function makeTool( 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 { showSnackBar } = useContext(CustomSnackBarContext); const [alternatesByVariable, setAlternatesByVariable] = useState<{ [key: string]: { value: { @@ -126,7 +117,7 @@ export default async function makeTool( } } - const selectionData = calcData.selections?.find( + const selectionData = calcData.presets?.find( (sel) => sel.title === selection ); @@ -142,12 +133,11 @@ export default async function makeTool( updateVarField( key, - dataTableLookup(dataTables[selectionData.source], preset)[ + dataTableLookup(selectionData.source, preset)[ selectionData.bind[key] ], - dataTables[selectionData.source].columns[selectionData.bind[key]] - ?.unit || '', + selectionData.source.columns[selectionData.bind[key]]?.unit || '', currentValues, updateFieldFunc ); @@ -178,19 +168,16 @@ export default async function makeTool( } }); - calcData.selections?.forEach((selection) => { + calcData.presets?.forEach((selection) => { initialValues.presets[selection.title] = selection.default; if (selection.default == '') return; for (const key in selection.bind) { initialValues.vars[key] = { - value: dataTableLookup( - dataTables[selection.source], - selection.default - )[selection.bind[key]], + value: dataTableLookup(selection.source, selection.default)[ + selection.bind[key] + ], - unit: - dataTables[selection.source].columns[selection.bind[key]]?.unit || - '' + unit: selection.source.columns[selection.bind[key]]?.unit || '' }; valsBoundToPreset[key] = selection.title; } @@ -264,53 +251,55 @@ export default async function makeTool( calcData.formula }} getGroups={({ values, updateField }) => [ - { - title: 'Presets', - component: ( - - - - - Option - Value - - - - {calcData.selections?.map((preset) => ( - - {preset.title} - - ', - ...Object.keys( - dataTables[preset.source].data - ).sort() - ]} - sx={{ width: 300 }} - onChange={(event, newValue) => { - handleSelectedPresetChange( - preset.title, - newValue || '', - values, - updateField - ); - }} - renderInput={(params) => ( - - )} - > - - - ))} - -
-
- ) - }, + ...(calcData.presets?.length + ? [ + { + title: 'Presets', + component: ( + + + + + Option + Value + + + + {calcData.presets?.map((preset) => ( + + {preset.title} + + ', + ...Object.keys(preset.source.data).sort() + ]} + sx={{ width: 300 }} + onChange={(event, newValue) => { + handleSelectedPresetChange( + preset.title, + newValue || '', + values, + updateField + ); + }} + renderInput={(params) => ( + + )} + > + + + ))} + +
+
+ ) + } + ] + : []), { title: 'Variables', component: ( @@ -330,9 +319,6 @@ export default async function makeTool( {variable.title}
@@ -362,9 +347,6 @@ export default async function makeTool( { if (values.outputVariable === '') { - setResult('Please select a solve for variable'); + showSnackBar('Please select a solve for variable', 'error'); return; } let expr: nerdamer.Expression | null = null; @@ -491,8 +473,6 @@ export default async function makeTool( 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( diff --git a/src/pages/tools/number/generic-calc/service.ts b/src/pages/tools/number/generic-calc/service.ts deleted file mode 100644 index 886ff10..0000000 --- a/src/pages/tools/number/generic-calc/service.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { InitialValuesType } from './types'; - -export function main(input: string, options: InitialValuesType): string { - return input + 'pp'; -} From 1b72557aa21a5f6b6f4b63fa06c607dc5d288402 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sat, 5 Apr 2025 05:36:06 +0000 Subject: [PATCH 17/53] refactor: remove unnecessary state and ui improvements --- .idea/workspace.xml | 40 ++-- src/components/ToolContent.tsx | 6 +- src/components/input/NumericInputWithUnit.tsx | 2 - src/components/options/ToolOptions.tsx | 5 +- src/pages/tools/number/generic-calc/index.tsx | 189 +++++++++--------- 5 files changed, 115 insertions(+), 127 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ca23a2a..3e13e70 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,26 +4,12 @@
))} - - {calcData.extraOutputs?.map((extraOutput) => ( +
+ + ) + }, + ...(calcData.extraOutputs + ? [ + { + title: 'Extra outputs', + component: calcData.extraOutputs?.map((extraOutput) => ( {extraOutput.title} @@ -412,15 +406,14 @@ export default async function makeTool( value: extraOutputs[extraOutput.title], unit: extraOutput.unit }} - > + /> - ))} - - - ) - } + )) + } + ] + : []) ]} compute={(values) => { if (values.outputVariable === '') { @@ -429,10 +422,10 @@ export default async function makeTool( } let expr: nerdamer.Expression | null = null; - for (const i of calcData.variables) { - if (i.name === values.outputVariable) { - if (i.formula !== undefined) { - expr = nerdamer(i.formula); + for (const variable of calcData.variables) { + if (variable.name === values.outputVariable) { + if (variable.formula !== undefined) { + expr = nerdamer(variable.formula); } } } @@ -442,7 +435,6 @@ export default async function makeTool( } if (expr == null) { throw new Error('No formula found'); - return; } Object.keys(values.vars).forEach((key) => { @@ -463,10 +455,15 @@ export default async function makeTool( if ((result as unknown as nerdamer.Expression[])?.length < 1) { values.vars[values.outputVariable].value = NaN; if (calcData.extraOutputs !== undefined) { - for (let i = 0; i < calcData.extraOutputs.length; i++) { - const extraOutput = calcData.extraOutputs[i]; - extraOutputs[extraOutput.title] = NaN; - } + // Update extraOutputs using setState + setExtraOutputs((prevState) => { + const newState = { ...prevState }; + for (let i = 0; i < calcData.extraOutputs!.length; i++) { + const extraOutput = calcData.extraOutputs![i]; + newState[extraOutput.title] = NaN; + } + return newState; + }); } throw new Error('No solution found for this input'); } @@ -500,9 +497,11 @@ export default async function makeTool( const result: nerdamer.Expression = expr.evaluate(); if (result) { - extraOutputs[extraOutput.title] = parseFloat( - result.toDecimal() - ); + // Update extraOutputs state properly + setExtraOutputs((prevState) => ({ + ...prevState, + [extraOutput.title]: parseFloat(result.toDecimal()) + })); } } } From da7e2726fa1d6a31c8b726601af9e0d85df6fa4e Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sat, 5 Apr 2025 06:10:25 +0000 Subject: [PATCH 18/53] chore: make responsive --- src/components/input/NumericInputWithUnit.tsx | 20 +- src/pages/tools/number/generic-calc/index.tsx | 217 ++++++++++-------- 2 files changed, 130 insertions(+), 107 deletions(-) diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index 25ae976..a9e0212 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -5,7 +5,7 @@ import Qty from 'js-quantities'; // const siPrefixes: { [key: string]: number } = { - '': 1, + 'Default prefix': 1, k: 1000, M: 1000000, G: 1000000000, @@ -24,7 +24,7 @@ export default function NumericInputWithUnit(props: { defaultPrefix?: string; }) { const [inputValue, setInputValue] = useState(props.value.value); - const [prefix, setPrefix] = useState(props.defaultPrefix || ''); + const [prefix, setPrefix] = useState(props.defaultPrefix || 'Default prefix'); // internal display unit const [unit, setUnit] = useState(''); @@ -121,13 +121,8 @@ export default function NumericInputWithUnit(props: { }; return ( - - + + - + { setUserSelectedUnit(true); diff --git a/src/pages/tools/number/generic-calc/index.tsx b/src/pages/tools/number/generic-calc/index.tsx index 48ed78f..5ea172f 100644 --- a/src/pages/tools/number/generic-calc/index.tsx +++ b/src/pages/tools/number/generic-calc/index.tsx @@ -1,15 +1,4 @@ -import { - Autocomplete, - Box, - Radio, - Stack, - Table, - TableBody, - TableCell, - TableHead, - TableRow, - TextField -} from '@mui/material'; +import { Autocomplete, Box, Radio, Stack, TextField } from '@mui/material'; import React, { useContext, useState } from 'react'; import ToolContent from '@components/ToolContent'; import { ToolComponentProps } from '@tools/defineTool'; @@ -290,60 +279,41 @@ export default async function makeTool( { title: 'Variables', component: ( - - - - Value - Solve For - - - - {calcData.variables.map((variable) => ( - - -
- - {variable.title} - - - updateVarField( - variable.name, - val.value, - val.unit, - values, - updateField - ) - } - /> - - + + + + + + Solve For + + + - {variable.alternates?.map((alt) => ( - - {alt.title} - + {calcData.variables.map((variable) => ( + + + + + + + + + {variable.title} + updateVarField( variable.name, - getMainFromAlternate(alt, variable, val), - variable.unit, + val.value, + val.unit, values, updateField ) } - > - - - ))} -
- + /> + + - + {variable.alternates?.map((alt) => ( + + + + {alt.title} + + + + updateVarField( + variable.name, + getMainFromAlternate( + alt, + variable, + val + ), + variable.unit, + values, + updateField + ) + } + /> + + + + ))} + + +
+ + - - - ))} - - + +
+ + ))} + ) }, ...(calcData.extraOutputs ? [ { title: 'Extra outputs', - component: calcData.extraOutputs?.map((extraOutput) => ( - - - {extraOutput.title} - - - - - )) + component: ( + + {calcData.extraOutputs?.map((extraOutput) => ( + + + {extraOutput.title} + + + + ))} + + ) } ] : []) From 2f0970c49bfc8aedd715ee311af20aa1fc374d49 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Mon, 7 Apr 2025 22:45:13 +0100 Subject: [PATCH 19/53] fix: redundant units --- .idea/workspace.xml | 105 +++++++++--------- src/components/input/NumericInputWithUnit.tsx | 5 +- .../tools/number/generic-calc/data/ohmsLaw.ts | 6 +- .../generic-calc/data/voltageDropInWire.ts | 2 +- 4 files changed, 57 insertions(+), 61 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3e13e70..d7eac4b 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,10 +6,9 @@ - - - + + - { + "keyToString": { + "ASKED_ADD_EXTERNAL_FILES": "true", + "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", + "Docker.Dockerfile build.executor": "Run", + "Docker.Dockerfile.executor": "Run", + "Playwright.Create transparent PNG.should make png color transparent.executor": "Run", + "Playwright.JoinText Component.executor": "Run", + "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "Vitest.compute function (1).executor": "Run", + "Vitest.compute function.executor": "Run", + "Vitest.mergeText.executor": "Run", + "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run", + "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run", + "Vitest.parsePageRanges.executor": "Run", + "Vitest.removeDuplicateLines function.executor": "Run", + "Vitest.removeDuplicateLines function.newlines option.executor": "Run", + "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run", + "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", + "Vitest.replaceText function.executor": "Run", + "Vitest.timeBetweenDates.executor": "Run", + "git-widget-placeholder": "#89 on generic-calc", + "ignore.virus.scanning.warn.message": "true", + "kotlin-language-version-configured": "true", + "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/@types", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "npm.build.executor": "Run", + "npm.dev.executor": "Run", + "npm.lint.executor": "Run", + "npm.prebuild.executor": "Run", + "npm.script:create:tool.executor": "Run", + "npm.test.executor": "Run", + "npm.test:e2e.executor": "Run", + "npm.test:e2e:run.executor": "Run", + "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier", + "project.structure.last.edited": "Problems", + "project.structure.proportion": "0.0", + "project.structure.side.proportion": "0.2", + "settings.editor.selected.configurable": "refactai_advanced_settings", + "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib", + "vue.rearranger.settings.migration": "true" } -}]]> +} - + - - + + + + + - + - - - - - + + + + - { - "prStates": [ + +}]]> { "selectedUrlAndAccountId": { "url": "https://github.com/iib0011/omni-tools.git", @@ -162,56 +168,56 @@ - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", - "Docker.Dockerfile build.executor": "Run", - "Docker.Dockerfile.executor": "Run", - "Playwright.Create transparent PNG.should make png color transparent.executor": "Run", - "Playwright.JoinText Component.executor": "Run", - "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "Vitest.compute function (1).executor": "Run", - "Vitest.compute function.executor": "Run", - "Vitest.mergeText.executor": "Run", - "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run", - "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run", - "Vitest.parsePageRanges.executor": "Run", - "Vitest.removeDuplicateLines function.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run", - "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", - "Vitest.replaceText function.executor": "Run", - "Vitest.timeBetweenDates.executor": "Run", - "git-widget-placeholder": "main", - "ignore.virus.scanning.warn.message": "true", - "kotlin-language-version-configured": "true", - "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "npm.build.executor": "Run", - "npm.dev.executor": "Run", - "npm.lint.executor": "Run", - "npm.prebuild.executor": "Run", - "npm.script:create:tool.executor": "Run", - "npm.test.executor": "Run", - "npm.test:e2e.executor": "Run", - "npm.test:e2e:run.executor": "Run", - "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier", - "project.structure.last.edited": "Problems", - "project.structure.proportion": "0.0", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "refactai_advanced_settings", - "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -857,8 +866,6 @@ diff --git a/src/components/input/ToolMultiplePdfInput.tsx b/src/components/input/ToolMultiplePdfInput.tsx index f7d551a..2afa2ff 100644 --- a/src/components/input/ToolMultiplePdfInput.tsx +++ b/src/components/input/ToolMultiplePdfInput.tsx @@ -14,6 +14,7 @@ interface MultiPdfInputComponentProps { value: MultiPdfInput[]; onChange: (file: MultiPdfInput[]) => void; } + export interface MultiPdfInput { file: File; order: number; @@ -43,20 +44,6 @@ export default function ToolMultiFileInput({ fileInputRef.current?.click(); }; - const handleCopy = () => { - if (isArray(value)) { - const blob = new Blob([value[0].file], { type: value[0].file.type }); - const clipboardItem = new ClipboardItem({ [value[0].file.type]: blob }); - - navigator.clipboard - .write([clipboardItem]) - .then(() => showSnackBar('File copied', 'success')) - .catch((err) => { - showSnackBar('Failed to copy: ' + err, 'error'); - }); - } - }; - function handleClear() { onChange([]); } @@ -175,11 +162,7 @@ export default function ToolMultiFileInput({ - + ); diff --git a/src/pages/tools/pdf/index.ts b/src/pages/tools/pdf/index.ts index 6e02856..0e2627b 100644 --- a/src/pages/tools/pdf/index.ts +++ b/src/pages/tools/pdf/index.ts @@ -2,15 +2,13 @@ import { tool as pdfRotatePdf } from './rotate-pdf/meta'; import { meta as splitPdfMeta } from './split-pdf/meta'; import { meta as mergePdf } from './merge-pdf/meta'; import { DefinedTool } from '@tools/defineTool'; - -export const pdfTools: DefinedTool[] = [splitPdfMeta, pdfRotatePdf, mergePdf]; import { tool as compressPdfTool } from './compress-pdf/meta'; import { tool as protectPdfTool } from './protect-pdf/meta'; -import { DefinedTool } from '@tools/defineTool'; export const pdfTools: DefinedTool[] = [ splitPdfMeta, pdfRotatePdf, compressPdfTool, - protectPdfTool + protectPdfTool, + mergePdf ]; diff --git a/src/pages/tools/pdf/merge-pdf/index.tsx b/src/pages/tools/pdf/merge-pdf/index.tsx index 5601dad..789da77 100644 --- a/src/pages/tools/pdf/merge-pdf/index.tsx +++ b/src/pages/tools/pdf/merge-pdf/index.tsx @@ -7,7 +7,7 @@ import ToolMultiPdfInput, { MultiPdfInput } from '@components/input/ToolMultiplePdfInput'; -export default function SplitPdf({ title }: ToolComponentProps) { +export default function MergePdf({ title }: ToolComponentProps) { const [input, setInput] = useState([]); const [result, setResult] = useState(null); const [isProcessing, setIsProcessing] = useState(false); @@ -35,19 +35,18 @@ export default function SplitPdf({ title }: ToolComponentProps) { setInput={setInput} initialValues={input.map((i) => i.file)} compute={compute} - // exampleCards={exampleCards} inputComponent={ { - setInput(v); + onChange={(pdfInputs) => { + setInput(pdfInputs); }} accept={['application/pdf']} title={'Input PDF'} type="pdf" /> } - getGroups={({ values, updateField }) => []} + getGroups={null} resultComponent={ Date: Tue, 13 May 2025 22:52:13 +0100 Subject: [PATCH 38/53] fix: unnecessary --- .idea/workspace.xml | 33 +++++++++++------------- src/pages/tools/pdf/rotate-pdf/index.tsx | 4 +-- src/pages/tools/pdf/split-pdf/index.tsx | 6 +---- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index a9be707..59e1902 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,12 +4,9 @@ - + - - - - + @@ -866,8 +863,6 @@ diff --git a/src/pages/tools/pdf/rotate-pdf/index.tsx b/src/pages/tools/pdf/rotate-pdf/index.tsx index 9ecb9e0..2e41f2a 100644 --- a/src/pages/tools/pdf/rotate-pdf/index.tsx +++ b/src/pages/tools/pdf/rotate-pdf/index.tsx @@ -139,9 +139,7 @@ export default function RotatePdf({ inputComponent={ { - setInput(isArray(v) ? v[0] : v); - }} + onChange={setInput} accept={['application/pdf']} title={'Input PDF'} /> diff --git a/src/pages/tools/pdf/split-pdf/index.tsx b/src/pages/tools/pdf/split-pdf/index.tsx index 4cf2787..3ceacbd 100644 --- a/src/pages/tools/pdf/split-pdf/index.tsx +++ b/src/pages/tools/pdf/split-pdf/index.tsx @@ -8,7 +8,6 @@ import { parsePageRanges, splitPdf } from './service'; import { CardExampleType } from '@components/examples/ToolExamples'; import { PDFDocument } from 'pdf-lib'; import ToolPdfInput from '@components/input/ToolPdfInput'; -import { isArray } from 'lodash'; type InitialValuesType = { pageRanges: string; @@ -117,10 +116,7 @@ export default function SplitPdf({ title }: ToolComponentProps) { inputComponent={ { - setInput(isArray(v) ? v[0] : v); - }} - multiple={false} + onChange={setInput} accept={['application/pdf']} title={'Input PDF'} /> From 3149ae7cef49ee41f1ee258befdadeefaa2817cc Mon Sep 17 00:00:00 2001 From: nevolodia Date: Thu, 22 May 2025 16:58:22 +0200 Subject: [PATCH 39/53] Main flip page tool and meta added. --- src/pages/tools/video/flip/index.tsx | 113 +++++++++++++++++++++++++++ src/pages/tools/video/flip/meta.ts | 15 ++++ 2 files changed, 128 insertions(+) create mode 100644 src/pages/tools/video/flip/index.tsx create mode 100644 src/pages/tools/video/flip/meta.ts diff --git a/src/pages/tools/video/flip/index.tsx b/src/pages/tools/video/flip/index.tsx new file mode 100644 index 0000000..c01fd41 --- /dev/null +++ b/src/pages/tools/video/flip/index.tsx @@ -0,0 +1,113 @@ +import { Box } from '@mui/material'; +import { useCallback, useState } from 'react'; +import * as Yup from 'yup'; +import ToolFileResult from '@components/result/ToolFileResult'; +import ToolContent from '@components/ToolContent'; +import { ToolComponentProps } from '@tools/defineTool'; +import { GetGroupsType } from '@components/options/ToolOptions'; +import { debounce } from 'lodash'; +import ToolVideoInput from '@components/input/ToolVideoInput'; +import { flipVideo } from './service'; +import { FlipOrientation, InitialValuesType } from './types'; +import SimpleRadio from '@components/options/SimpleRadio'; + +export const initialValues: InitialValuesType = { + orientation: 'horizontal' +}; + +export const validationSchema = Yup.object({ + orientation: Yup.string() + .oneOf( + ['horizontal', 'vertical'], + 'Orientation must be horizontal or vertical' + ) + .required('Orientation is required') +}); + +const orientationOptions: { value: FlipOrientation; label: string }[] = [ + { value: 'horizontal', label: 'Horizontal (Mirror)' }, + { value: 'vertical', label: 'Vertical (Upside Down)' } +]; + +export default function FlipVideo({ title }: ToolComponentProps) { + const [input, setInput] = useState(null); + const [result, setResult] = useState(null); + const [loading, setLoading] = useState(false); + + const compute = async ( + optionsValues: InitialValuesType, + input: File | null + ) => { + if (!input) return; + setLoading(true); + + try { + const flippedFile = await flipVideo(input, optionsValues.orientation); + setResult(flippedFile); + } catch (error) { + console.error('Error flipping video:', error); + } finally { + setLoading(false); + } + }; + + const debouncedCompute = useCallback(debounce(compute, 1000), []); + + const getGroups: GetGroupsType = ({ + values, + updateField + }) => [ + { + title: 'Orientation', + component: ( + + {orientationOptions.map((orientationOption) => ( + { + updateField('orientation', orientationOption.value); + }} + /> + ))} + + ) + } + ]; + + return ( + + } + resultComponent={ + loading ? ( + + ) : ( + + ) + } + initialValues={initialValues} + getGroups={getGroups} + compute={debouncedCompute} + setInput={setInput} + validationSchema={validationSchema} + /> + ); +} diff --git a/src/pages/tools/video/flip/meta.ts b/src/pages/tools/video/flip/meta.ts new file mode 100644 index 0000000..3462925 --- /dev/null +++ b/src/pages/tools/video/flip/meta.ts @@ -0,0 +1,15 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('video', { + name: 'Flip Video', + path: 'flip', + icon: 'mdi:flip-horizontal', + description: + 'This online utility allows you to flip videos horizontally or vertically. You can preview the flipped video before processing. Supports common video formats like MP4, WebM, and OGG.', + shortDescription: 'Flip videos horizontally or vertically', + keywords: ['flip', 'video', 'mirror', 'edit', 'horizontal', 'vertical'], + longDescription: + 'Easily flip your videos horizontally (mirror) or vertically (upside down) with this simple online tool.', + component: lazy(() => import('./index')) +}); From b6b3b6a64370d223b2d3c0808efa4cea1d75eb3b Mon Sep 17 00:00:00 2001 From: nevolodia Date: Thu, 22 May 2025 16:58:42 +0200 Subject: [PATCH 40/53] Video flip logic added. --- src/pages/tools/video/flip/service.ts | 43 +++++++++++++++++++++++++++ src/pages/tools/video/flip/types.ts | 5 ++++ 2 files changed, 48 insertions(+) create mode 100644 src/pages/tools/video/flip/service.ts create mode 100644 src/pages/tools/video/flip/types.ts diff --git a/src/pages/tools/video/flip/service.ts b/src/pages/tools/video/flip/service.ts new file mode 100644 index 0000000..873d769 --- /dev/null +++ b/src/pages/tools/video/flip/service.ts @@ -0,0 +1,43 @@ +import { FFmpeg } from '@ffmpeg/ffmpeg'; +import { fetchFile } from '@ffmpeg/util'; +import { FlipOrientation } from './types'; + +const ffmpeg = new FFmpeg(); + +export async function flipVideo( + input: File, + orientation: FlipOrientation +): Promise { + if (!ffmpeg.loaded) { + await ffmpeg.load({ + wasmURL: + 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/esm/ffmpeg-core.wasm' + }); + } + + const inputName = 'input.mp4'; + const outputName = 'output.mp4'; + await ffmpeg.writeFile(inputName, await fetchFile(input)); + + const flipMap: Record = { + horizontal: 'hflip', + vertical: 'vflip' + }; + const flipFilter = flipMap[orientation]; + + const args = ['-i', inputName]; + if (flipFilter) { + args.push('-vf', flipFilter); + } + + args.push('-c:v', 'libx264', '-preset', 'ultrafast', outputName); + + await ffmpeg.exec(args); + + const flippedData = await ffmpeg.readFile(outputName); + return new File( + [new Blob([flippedData], { type: 'video/mp4' })], + `${input.name.replace(/\.[^/.]+$/, '')}_flipped.mp4`, + { type: 'video/mp4' } + ); +} diff --git a/src/pages/tools/video/flip/types.ts b/src/pages/tools/video/flip/types.ts new file mode 100644 index 0000000..6f2258c --- /dev/null +++ b/src/pages/tools/video/flip/types.ts @@ -0,0 +1,5 @@ +export type FlipOrientation = 'horizontal' | 'vertical'; + +export type InitialValuesType = { + orientation: FlipOrientation; +}; From 6af29573140c641e69ff90338c7aea9a5455abe8 Mon Sep 17 00:00:00 2001 From: nevolodia Date: Thu, 22 May 2025 16:59:00 +0200 Subject: [PATCH 41/53] Video flip tool added to main page. --- src/pages/tools/video/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/tools/video/index.ts b/src/pages/tools/video/index.ts index ca3e413..3e6659d 100644 --- a/src/pages/tools/video/index.ts +++ b/src/pages/tools/video/index.ts @@ -1,14 +1,17 @@ +import { tool as videoFlip } from './flip/meta'; import { rotate } from '../string/rotate/service'; import { gifTools } from './gif'; import { tool as trimVideo } from './trim/meta'; import { tool as rotateVideo } from './rotate/meta'; import { tool as compressVideo } from './compress/meta'; import { tool as loopVideo } from './loop/meta'; +import { tool as flipVideo } from './flip/meta'; export const videoTools = [ ...gifTools, trimVideo, rotateVideo, compressVideo, - loopVideo + loopVideo, + flipVideo ]; From 0a68edfbadfd5f1bc28c192a8c521b2a2a717cc2 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Thu, 22 May 2025 17:18:36 +0100 Subject: [PATCH 42/53] fix: missing previews --- .idea/workspace.xml | 16 +++++++++++++--- src/components/input/BaseFileInput.tsx | 13 +++++-------- src/pages/tools/pdf/protect-pdf/service.ts | 1 - 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 59e1902..8b52a7f 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,7 +6,8 @@ - + + @@ -189,7 +197,7 @@ "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", "Vitest.replaceText function.executor": "Run", "Vitest.timeBetweenDates.executor": "Run", - "git-widget-placeholder": "#102 on fork/rohit267/feat/pdf-merge", + "git-widget-placeholder": "#117 on fork/nevolodia/flip-video", "ignore.virus.scanning.warn.message": "true", "kotlin-language-version-configured": "true", "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src", @@ -423,6 +431,8 @@ + +