Initial alpha version of the Ratio Calculator.

This commit is contained in:
Roland 2025-01-18 10:08:11 -07:00
parent f321e9f6f3
commit fb187e1305
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
8 changed files with 474 additions and 32 deletions

156
app/package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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: <ElectricalCalculators/>},
{key: 'antenna', icon: 'signal', button: 'Antenna', contents: <DipoleAntennaCalculator/>},
{key: 'temperature', icon: 'thermometer', button: 'Temperature', contents: <TemperatureCalculator/>},
{key: ' ratio', icon: 'th large', button: 'Ratio', contents: <RatioCalculators/>},
];
const activeCalculator = calc ?

View file

@ -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 <Search> input with a Clear button.
@ -544,7 +544,7 @@ export function SearchResultsInput({
return <Form onSubmit={localOnSubmit} {...props} className='search-container'>
<Search category
input={{fluid: true, icon: icon}}
input={{fluid: true, icon: icon, ref: inputRef}}
placeholder={placeholder}
type='text'
onSearchChange={handleChange}

View file

@ -97,8 +97,9 @@ export function NavBar() {
// Red/Yellow colors will blend in to some navbar colors, swap the colors for high contrast.
const conflictingColors = ['red', 'orange', 'yellow', 'olive', 'pink'];
const lowWarningColor = conflictingColors.includes(navColor) ? 'white' : 'yellow';
const highWarningColor = conflictingColors.includes(navColor) ? 'black' : 'red';
const [lowWarningColor, highWarningColor] = conflictingColors.includes(navColor)
? [null, 'black']
: ['yellow', 'red'];
// Generic system load, the least important warning.
const {minute_1, mediumLoad, highLoad} = useLoad();
@ -172,7 +173,6 @@ export function NavBar() {
processingLink = '/map/manage';
}
}
const processingIcon = processingLink &&
<Link to={processingLink}>
<Icon loading name='circle notch' size='large'/>

View file

@ -400,23 +400,37 @@ export function SearchIconButton() {
prevOpen.current = open;
}, [open]);
const inputRef = React.useRef();
React.useEffect(() => {
// Focus on the Search's <input/> when the modal is opened.
if (open) {
inputRef.current.focus();
}
}, [open]);
let modalContents;
if (open) {
modalContents = <SearchResultsInput clearable
searchStr={searchStr}
onChange={setSearchStr}
onSubmit={setSearchStr}
size='large'
placeholder='Search everywhere...'
results={suggestionsResults}
handleResultSelect={localHandleResultSelect}
resultRenderer={resultRenderer}
loading={loading}
inputRef={inputRef}
/>;
}
return <React.Fragment>
<a className='item' style={{paddingRight: '0.7em'}} onClick={() => setOpen(true)}>
<Icon name='search'/>
</a>
<Modal open={open} onClose={() => setOpen(false)} centered={false}>
<ModalContent>
<SearchResultsInput clearable
searchStr={searchStr}
onChange={setSearchStr}
onSubmit={setSearchStr}
size='large'
placeholder='Search everywhere...'
results={suggestionsResults}
handleResultSelect={localHandleResultSelect}
resultRenderer={resultRenderer}
loading={loading}
/>
{modalContents}
</ModalContent>
</Modal>
</React.Fragment>

View file

@ -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 = <Toggle
@ -299,6 +297,7 @@ const PowerLossCalculator = ({volts, setVolts}) => {
type='number'
value={length}
onChange={(e, {value}) => setLength(parseInt(value))}
autocomplete="off"
/>
</Grid.Column>
<Grid.Column mobile={4} tablet={2}>

View file

@ -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 <Form>
<Header as='h1'>Ratio</Header>
<Grid columns={3}>
<Grid.Row columns={1}>
<Grid.Column>
<Dropdown selection
fluid
placeholder='base'
options={baseOptions}
value={state.unit}
name='base'
onChange={(e, data) => dispatch(data)}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row columns={1}>
<Grid.Column>
<Header as='h2'>A : B = C : D</Header>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column width={8}>
<div className="ui fluid labeled input">
<div className={`ui label ${aColor}`}>A</div>
<input
id="a"
name="a"
type="number"
value={state.a}
onChange={e => dispatch(e)}
onFocus={e => e.target.select()}
/>
{state.base &&
<Dropdown {...unitDropdownOptions} name='aUnit' value={state.aUnit}/>}
</div>
</Grid.Column>
<Grid.Column width={1}/>
<Grid.Column width={7}>
<div className="ui fluid labeled input">
<div className={`ui label ${cColor}`}>C</div>
<input
id="c"
name="c"
type="number"
value={state.c}
onChange={e => dispatch(e)}
onFocus={e => e.target.select()}
/>
{state.base &&
<Dropdown {...unitDropdownOptions} name='cUnit' value={state.cUnit}/>}
</div>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column width={8}>
<hr/>
</Grid.Column>
<Grid.Column textAlign='center' width={1}>
<Header as='h1'>=</Header>
</Grid.Column>
<Grid.Column width={7}>
<hr/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column width={8}>
<div className="ui fluid labeled input">
<div className={`ui label ${bColor}`}>B</div>
<input
id="b"
name="b"
type="number"
value={state.b}
onChange={e => dispatch(e)}
onFocus={e => e.target.select()}
/>
{state.base &&
<Dropdown {...unitDropdownOptions} name='bUnit' value={state.bUnit}/>}
</div>
</Grid.Column>
<Grid.Column width={1}/>
<Grid.Column width={7}>
<div className="ui fluid labeled input">
<div className={`ui label ${dColor}`}>D</div>
<input
id="d"
name="d"
type="number"
value={state.d}
onChange={e => dispatch(e)}
onFocus={e => e.target.select()}
/>
{state.base &&
<Dropdown {...unitDropdownOptions} name='dUnit' value={state.dUnit}/>}
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</Form>
}
export const RatioCalculators = () => {
return <Container>
<RatioCalculator/>
</Container>
}