diff --git a/app/package-lock.json b/app/package-lock.json index ee7f605b..b4f234f0 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/user-event": "^13.5.0", "cypress": "^13.15.2", "lodash": "^4.17.21", + "mathjs": "^14.0.1", "query-string": "^8.1.0", "react": "^18.2.0", "react-colorful": "^5.6.1", @@ -1814,16 +1815,21 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -5930,6 +5936,18 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "node_modules/complex.js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz", + "integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -7416,6 +7434,11 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -10346,6 +10369,11 @@ "node": ">=8" } }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, "node_modules/jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", @@ -12813,6 +12841,40 @@ "tmpl": "1.0.5" } }, + "node_modules/mathjs": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.0.1.tgz", + "integrity": "sha512-yyJgLwC6UXuve724np8tHRMYaTtb5UqiOGQkjwbSXgH8y1C/LcJ0pvdNDZLI2LT7r+iExh2Y5HwfAY+oZFtGIQ==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "complex.js": "^2.2.5", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^5.2.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mathjs/node_modules/fraction.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.2.1.tgz", + "integrity": "sha512-Ah6t/7YCYjrPUFUFsOsRLMXAdnYM+aQwmojD2Ayb/Ezr82SwES0vuyQ8qZ3QO8n9j7W14VJuVZZet8U3bhSdQQ==", + "engines": { + "node": ">= 12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -16147,6 +16209,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -17273,6 +17340,11 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -17505,6 +17577,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz", + "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==", + "engines": { + "node": ">= 18" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -19927,11 +20007,18 @@ } }, "@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } } }, "@babel/template": { @@ -22873,6 +22960,11 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "complex.js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.2.tgz", + "integrity": "sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==" + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -23955,6 +24047,11 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -26024,6 +26121,11 @@ } } }, + "javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, "jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", @@ -27840,6 +27942,29 @@ "tmpl": "1.0.5" } }, + "mathjs": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.0.1.tgz", + "integrity": "sha512-yyJgLwC6UXuve724np8tHRMYaTtb5UqiOGQkjwbSXgH8y1C/LcJ0pvdNDZLI2LT7r+iExh2Y5HwfAY+oZFtGIQ==", + "requires": { + "@babel/runtime": "^7.25.7", + "complex.js": "^2.2.5", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^5.2.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "dependencies": { + "fraction.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.2.1.tgz", + "integrity": "sha512-Ah6t/7YCYjrPUFUFsOsRLMXAdnYM+aQwmojD2Ayb/Ezr82SwES0vuyQ8qZ3QO8n9j7W14VJuVZZet8U3bhSdQQ==" + } + } + }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -30019,6 +30144,11 @@ "ajv-keywords": "^3.5.2" } }, + "seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -30898,6 +31028,11 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -31076,6 +31211,11 @@ "is-typed-array": "^1.1.9" } }, + "typed-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.1.tgz", + "integrity": "sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==" + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", diff --git a/app/package.json b/app/package.json index 172a10f3..f6bbf51c 100644 --- a/app/package.json +++ b/app/package.json @@ -9,6 +9,7 @@ "@testing-library/user-event": "^13.5.0", "cypress": "^13.15.2", "lodash": "^4.17.21", + "mathjs": "^14.0.1", "query-string": "^8.1.0", "react": "^18.2.0", "react-colorful": "^5.6.1", diff --git a/app/src/components/Calculators.js b/app/src/components/Calculators.js index d8cc406a..82aae8cc 100644 --- a/app/src/components/Calculators.js +++ b/app/src/components/Calculators.js @@ -6,6 +6,7 @@ import {TemperatureCalculator} from "./calculators/TemperatureCalculator"; import {ElectricalCalculators} from "./calculators/ElectricalCalculators"; import {DipoleAntennaCalculator} from "./calculators/HamCalculators"; import {Link} from "react-router-dom"; +import {RatioCalculators} from "./calculators/RatioCalculator"; export function CalculatorsPage() { const {calc, setCalc} = useCalcQuery(); @@ -14,6 +15,7 @@ export function CalculatorsPage() { {key: 'electrical', icon: 'lightning', button: 'Electrical', contents: }, {key: 'antenna', icon: 'signal', button: 'Antenna', contents: }, {key: 'temperature', icon: 'thermometer', button: 'Temperature', contents: }, + {key: ' ratio', icon: 'th large', button: 'Ratio', contents: }, ]; const activeCalculator = calc ? diff --git a/app/src/components/Common.js b/app/src/components/Common.js index 28e05413..4dc506c6 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -512,11 +512,11 @@ export function SearchResultsInput({ actionIcon, clearable = false, clearDisabled = null, - autoFocus = false, results = undefined, handleResultSelect = null, resultRenderer = undefined, loading = false, + inputRef = null, ...props }) { // A Semantic input with a Clear button. @@ -544,7 +544,7 @@ export function SearchResultsInput({ return diff --git a/app/src/components/Search.js b/app/src/components/Search.js index 2e4adb13..0281c08b 100644 --- a/app/src/components/Search.js +++ b/app/src/components/Search.js @@ -400,23 +400,37 @@ export function SearchIconButton() { prevOpen.current = open; }, [open]); + const inputRef = React.useRef(); + React.useEffect(() => { + // Focus on the Search's when the modal is opened. + if (open) { + inputRef.current.focus(); + } + }, [open]); + + let modalContents; + if (open) { + modalContents = ; + } + return setOpen(true)}> setOpen(false)} centered={false}> - + {modalContents} diff --git a/app/src/components/calculators/ElectricalCalculators.js b/app/src/components/calculators/ElectricalCalculators.js index c6477734..921d49db 100644 --- a/app/src/components/calculators/ElectricalCalculators.js +++ b/app/src/components/calculators/ElectricalCalculators.js @@ -64,14 +64,10 @@ function ohmsLawReducer(state, action) { let {volts, amps, ohms, watts, lastUpdated} = state; // Only replace last updated when a different input was updated. - let newLastUpdated = lastUpdated; - if (lastUpdated.length === 0) { - newLastUpdated = [type]; - } else if (lastUpdated.length === 1 && lastUpdated[0] !== type) { - newLastUpdated = [...lastUpdated, type]; - } else if (lastUpdated.length > 1 && lastUpdated[1] !== type) { - newLastUpdated = [lastUpdated[1], type]; - } + let newLastUpdated = lastUpdated || []; + newLastUpdated = newLastUpdated.filter(i => i !== type); + newLastUpdated = [type, ...newLastUpdated]; + newLastUpdated = newLastUpdated.slice(0, 2); // Copy new value into it's respective state. Do not allow negative numbers. if (type === 'amps') { @@ -125,6 +121,7 @@ export function OhmsLawCalculator({setVolts}) { type: 'number', labelPosition: 'right', onSelect: e => e.target.select(), // Select the contents of the Input when a user selects it. + autocomplete: 'off', }; React.useEffect(() => { @@ -282,6 +279,7 @@ const PowerLossCalculator = ({volts, setVolts}) => { type='number' value={volts} onChange={(e, {value}) => setVolts(parseInt(value))} + autocomplete="off" />; const saeToggle = { type='number' value={length} onChange={(e, {value}) => setLength(parseInt(value))} + autocomplete="off" /> diff --git a/app/src/components/calculators/RatioCalculator.js b/app/src/components/calculators/RatioCalculator.js new file mode 100644 index 00000000..330b4ff4 --- /dev/null +++ b/app/src/components/calculators/RatioCalculator.js @@ -0,0 +1,286 @@ +import {Container, Dropdown} from "semantic-ui-react"; +import {Form, Header} from "../Theme"; +import Grid from "semantic-ui-react/dist/commonjs/collections/Grid"; +import {ThemeContext} from "../../contexts/contexts"; +import React from "react"; +import {roundDigits} from "../Common"; +import {createUnit, multiply, unit} from "mathjs"; + +const nullUnit = createUnit('null'); + +function ratioReducer(prevState, e) { + const {value, name} = Object.keys(e).indexOf('target') >= 0 ? e.target : e; + let {a, aUnit, b, bUnit, c, cUnit, d, dUnit, base, lastUpdated} = prevState; + console.log('ratioReducer', name, value, lastUpdated); + + a = unit(a, aUnit); + b = unit(b, bUnit); + c = unit(c, cUnit); + d = unit(d, dUnit); + + // Keep a stack of which inputs are updated. Limit it to the 3 most recent inputs. + let newLastUpdated = lastUpdated || []; + if (['a', 'b', 'c', 'd'].includes(name)) { + newLastUpdated = newLastUpdated.filter(i => i !== name); + newLastUpdated = [name, ...newLastUpdated]; + newLastUpdated = newLastUpdated.slice(0, 3); + } + + console.log('a', a.toString(), 'b', b.toString(), 'c', c.toString(), 'd', d.toString()); + + const calculateA = () => a = multiply(b, c).divide(d).to(aUnit); + const calculateB = () => b = multiply(a, d).divide(c).to(bUnit); + const calculateC = () => c = multiply(a, d).divide(b).to(cUnit); + const calculateD = () => d = multiply(b, c).divide(a).to(dUnit); + + if (name === 'aUnit') { + aUnit = value; + a = a.to(value); + } else if (name === 'bUnit') { + bUnit = value; + b = b.to(value); + } else if (name === 'cUnit') { + cUnit = value; + c = c.to(value); + } else if (name === 'dUnit') { + dUnit = value; + d = d.to(value); + } else if (name === 'a') { + a = unit(value, aUnit); + } else if (name === 'b') { + b = unit(value, bUnit); + } else if (name === 'c') { + c = unit(value, cUnit); + } else if (name === 'd') { + d = unit(value, dUnit); + } else if (name === 'base') { + // Base is changing, reset units and values. + base = value; + aUnit = bUnit = cUnit = dUnit = baseToUnitsMap[base][0]['key']; + a = b = c = d = unit(1, aUnit); + // Reset last updated. + newLastUpdated = []; + } + + // Calculate ratio of the input which hasn't been updated recently. But only if all other inputs have values. + if (lastUpdated.length === 3) { + if (!lastUpdated.includes('a')) { + calculateA(); + } else if (!lastUpdated.includes('b')) { + calculateB(); + } else if (!lastUpdated.includes('c')) { + calculateC(); + } else if (!lastUpdated.includes('d')) { + calculateD(); + } + } + + return { + base, + lastUpdated: newLastUpdated, + a: roundDigits(a.toNumber(), 3), + b: roundDigits(b.toNumber(), 3), + c: roundDigits(c.toNumber(), 3), + d: roundDigits(d.toNumber(), 3), + aUnit, + bUnit, + cUnit, + dUnit, + } +} + +const baseToUnitsMap = { + 'length': [ + {key: 'centimeter', value: 'centimeter', text: 'centimeter'}, + {key: 'feet', value: 'feet', text: 'feet'}, + {key: 'inch', value: 'inch', text: 'inch'}, + {key: 'kilometer', value: 'kilometer', text: 'kilometer'}, + {key: 'meter', value: 'meter', text: 'meter'}, + {key: 'mile', value: 'mile', text: 'mile'}, + ], + 'area': [ + {key: 'm2', value: 'm2', text: 'm²'}, + {key: 'sqin', value: 'sqin', text: 'square inch'}, + {key: 'sqyd', value: 'sqyd', text: 'square yard'}, + {key: 'sqmi', value: 'sqmi', text: 'square mile'}, + {key: 'acre', value: 'acre', text: 'acre'}, + {key: 'hectare', value: 'hectare', text: 'hectare'}, + ], + 'volume': [ + {key: 'cc', value: 'cc', text: 'cc'}, + {key: 'cuin', value: 'cuin', text: 'cuin'}, + {key: 'cup', value: 'cup', text: 'cup'}, + {key: 'fluidounce', value: 'fluidounce', text: 'fluidounce'}, + {key: 'gallon', value: 'gallon', text: 'gallon'}, + {key: 'liter', value: 'liter', text: 'liter'}, + {key: 'm3', value: 'm3', text: 'meters³'}, + {key: 'milliliter', value: 'milliliter', text: 'ml'}, + {key: 'tablespoon', value: 'tablespoon', text: 'tablespoon'}, + {key: 'teaspoon', value: 'teaspoon', text: 'teaspoon'}, + ], + 'mass': [ + {key: 'grain', value: 'grain', text: 'grain'}, + {key: 'gram', value: 'gram', text: 'gram'}, + {key: 'lbs', value: 'lbs', text: 'pound'}, + {key: 'stone', value: 'stone', text: 'stone'}, + {key: 'ton', value: 'ton', text: 'ton'}, + ], + 'energy': [ + {key: 'joule', value: 'joule', text: 'joule'}, + {key: 'Wh', value: 'Wh', text: 'Wh'}, + {key: 'BTU', value: 'BTU', text: 'BTU'}, + ], + 'power': [ + {key: 'watt', value: 'watt', text: 'watt'}, + {key: 'hp', value: 'hp', text: 'hp'}, + ], +} + +const initialState = { + base: null, + lastUpdated: [], + a: '', + b: '', + c: '', + d: '', + aUnit: nullUnit, + bUnit: nullUnit, + cUnit: nullUnit, + dUnit: nullUnit, +}; + +const RatioCalculator = () => { + const {t} = React.useContext(ThemeContext); + + const [state, dispatch] = React.useReducer(ratioReducer, initialState); + + const unitOptions = baseToUnitsMap[state.base]; + + const baseOptions = [ + {key: null, value: null, text: 'None'}, + {key: 'length', value: 'length', text: 'meter, feet, inch, etc.', label: 'Length'}, + {key: 'area', value: 'area', text: 'inch², m², etc.', label: 'Area'}, + {key: 'volume', value: 'volume', text: 'liter, m³, cup, etc.', label: 'Volume'}, + {key: 'mass', value: 'mass', text: 'gram, pound, etc.', label: 'Mass'}, + {key: 'energy', value: 'energy', text: 'joule, Wh, BTU', label: 'Energy'}, + {key: 'power', value: 'power', text: 'watt, horse power', label: 'Power'}, + ]; + + const unitDropdownOptions = { + options: unitOptions, + className: 'label', + onChange: (e, data) => dispatch(data), + }; + + const aColor = state.lastUpdated.indexOf('a') >= 0 ? '' : 'grey'; + const bColor = state.lastUpdated.indexOf('b') >= 0 ? '' : 'grey'; + const cColor = state.lastUpdated.indexOf('c') >= 0 ? '' : 'grey'; + const dColor = state.lastUpdated.indexOf('d') >= 0 ? '' : 'grey'; + + return + Ratio + + + + + dispatch(data)} + /> + + + + + A : B = C : D + + + + + + A + dispatch(e)} + onFocus={e => e.target.select()} + /> + {state.base && + } + + + + + + C + dispatch(e)} + onFocus={e => e.target.select()} + /> + {state.base && + } + + + + + + + + + = + + + + + + + + + B + dispatch(e)} + onFocus={e => e.target.select()} + /> + {state.base && + } + + + + + + D + dispatch(e)} + onFocus={e => e.target.select()} + /> + {state.base && + } + + + + + +} + +export const RatioCalculators = () => { + return + + +} \ No newline at end of file