diff --git a/app/src/components/Calculators.js b/app/src/components/Calculators.js
index 637749cf..47c1b11f 100644
--- a/app/src/components/Calculators.js
+++ b/app/src/components/Calculators.js
@@ -1,227 +1,10 @@
import React from "react";
-import {BackButton, roundDigits, useTitle} from "./Common";
+import {BackButton, useTitle} from "./Common";
import {Button, Header, Icon} from "./Theme";
-import {GridColumn, GridRow, Input} from "semantic-ui-react";
-import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import {useCalcQuery} from "../hooks/customHooks";
-import {ColoredInput} from "./Apps";
-
-function TemperatureCalculator() {
- const [state, setState] = React.useState({
- celsius: '',
- fahrenheit: '',
- kelvin: '',
- })
-
- const handleInputChange = (e, {name, value}) => {
- if (value === '') {
- // User has cleared the input;
- setState({volt: '', ohm: '', amp: ''});
- return
- }
-
- const numValue = parseFloat(value.endsWith('.') ? `${value}0` : value);
- console.debug(`Calculating ${value} ${name}`);
- let newCelsius;
- let newKelvin;
- let newFahrenheit;
- if (name === 'fahrenheit') {
- newFahrenheit = value;
- newCelsius = roundDigits((numValue - 32) / 1.8);
- newKelvin = roundDigits(newCelsius + 273.15);
- } else if (name === 'celsius') {
- newCelsius = value;
- newFahrenheit = roundDigits((numValue * 1.8) + 32);
- newKelvin = roundDigits(numValue + 273.15);
- } else if (name === 'kelvin') {
- newKelvin = value;
- newCelsius = roundDigits(numValue - 273.15);
- newFahrenheit = roundDigits((newCelsius * 1.8) + 32);
- }
- setState({
- celsius: newCelsius,
- fahrenheit: newFahrenheit,
- kelvin: newKelvin
- });
- }
-
- const handleClick = (e) => {
- e.target.select();
- }
-
- const commonInputProps = {
- fluid: true,
- labelPosition: 'right',
- type: 'number',
- onChange: handleInputChange,
- onClick: handleClick
- };
-
- return
-
-
-
-
-
-
-
-
-
-
-
-
-}
-
-const initialState = {
- volts: '',
- amps: '',
- ohms: '',
- watts: '',
- lastUpdated: [],
-};
-
-// Ohms Law functions. These take two values, and return the other two electrical values.
-const voltsAmps = (volts, amps) => {
- const watts = volts * amps;
- const ohms = volts / amps;
- return [roundDigits(watts) || 0, roundDigits(ohms) || 0]
-};
-const voltsOhms = (volts, ohms) => {
- const amps = volts / ohms;
- const watts = volts * amps;
- return [roundDigits(amps) || 0, roundDigits(watts) || 0]
-};
-const ampsOhms = (amps, ohms) => {
- const volts = amps * ohms;
- const watts = volts * amps;
- return [roundDigits(volts) || 0, roundDigits(watts) || 0]
-};
-const voltsWatts = (volts, watts) => {
- const amps = watts / volts;
- const ohms = volts / amps;
- return [roundDigits(amps) || 0, roundDigits(ohms) || 0]
-};
-const ampsWatts = (amps, watts) => {
- const volts = watts / amps;
- const ohms = volts / amps;
- return [roundDigits(volts) || 0, roundDigits(ohms) || 0]
-};
-const wattsOhms = (watts, ohms) => {
- const volts = Math.sqrt(watts * ohms);
- const amps = volts / ohms;
- return [roundDigits(volts), roundDigits(amps)]
-};
-
-function ohmsLawReducer(state, action) {
- const {type, value} = 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];
- }
-
- // Copy new value into it's respective state. Do not allow negative numbers.
- if (type === 'amps') {
- amps = Math.abs(value);
- } else if (type === 'volts') {
- volts = Math.abs(value);
- } else if (type === 'ohms') {
- ohms = Math.abs(value);
- } else if (type === 'watts') {
- watts = Math.abs(value);
- }
-
- // Sort the updated values, so we don't have to define everything twice.
- const sortedLastUpdated = [...newLastUpdated].sort().join(',');
- switch (sortedLastUpdated) {
- case 'amps,ohms':
- [volts, watts] = ampsOhms(amps, ohms);
- break;
- case 'amps,volts':
- [watts, ohms] = voltsAmps(volts, amps);
- break;
- case 'amps,watts':
- [volts, ohms] = ampsWatts(amps, watts);
- break;
- case 'ohms,volts':
- [amps, watts] = voltsOhms(volts, ohms);
- break;
- case 'ohms,watts':
- [volts, amps] = wattsOhms(watts, ohms);
- break;
- case 'volts,watts':
- [amps, ohms] = voltsWatts(volts, watts);
- break;
- }
-
- return {volts, amps, ohms, watts, lastUpdated: newLastUpdated};
-}
-
-function ElectricalCalculator() {
- // Provides a form which provides 4 inputs (volts, amps, ohms, watts), and calculates the missing values.
-
- const [state, dispatch] = React.useReducer(ohmsLawReducer, initialState);
-
- const calculateColor = (name) => {
- // Recently updated inputs are white, others are grey.
- return state.lastUpdated.indexOf(name) >= 0 ? null : 'grey';
- }
-
- const inputProps = {
- fluid: true,
- type: 'number',
- labelPosition: 'right',
- onSelect: e => e.target.select(), // Select the contents of the Input when a user selects it.
- };
-
- return
-
-
-
-
- dispatch({type: 'volts', value: e.target.value})}
- label='Volts'
- color={calculateColor('volts')}
- />
-
-
- dispatch({type: 'amps', value: e.target.value})}
- label='Amps'
- color={calculateColor('amps')}
- />
-
-
-
-
- dispatch({type: 'ohms', value: e.target.value})}
- label='Ohms'
- color={calculateColor('ohms')}
- />
-
-
- dispatch({type: 'watts', value: e.target.value})}
- label='Watts'
- color={calculateColor('watts')}
- />
-
-
-
-
-}
+import {TemperatureCalculator} from "./calculators/TemperatureCalculator";
+import {ElectricalCalculator} from "./calculators/ElectricalCalculators";
+import {DipoleAntennaCalculator} from "./calculators/HamCalculators";
export function CalculatorsPage() {
const {calc, setCalc} = useCalcQuery();
@@ -229,6 +12,7 @@ export function CalculatorsPage() {
const calculators = [
{key: 'temperature', icon: 'thermometer', button: 'Temperature', contents: },
{key: 'electrical', icon: 'lightning', button: 'Electrical', contents: },
+ {key: 'antenna', icon: 'signal', button: 'Antenna', contents: },
];
const activeCalculator = calc ?
diff --git a/app/src/components/Search.js b/app/src/components/Search.js
index 740943c4..03fee1d6 100644
--- a/app/src/components/Search.js
+++ b/app/src/components/Search.js
@@ -43,6 +43,11 @@ const SUGGESTED_APPS = [
title: 'Electrical Calculator',
description: 'Convert Volts, Ohms, Amps, Watts'
},
+ {
+ location: '/more/calculators?calc=antenna',
+ title: 'Antenna Calculator',
+ description: 'Calculate dipole length, wavelength.'
+ },
];
export const useSearch = (defaultLimit = 48, totalPages = 0, emptySearch = false, model) => {
diff --git a/app/src/components/calculators/ElectricalCalculators.js b/app/src/components/calculators/ElectricalCalculators.js
new file mode 100644
index 00000000..414b8963
--- /dev/null
+++ b/app/src/components/calculators/ElectricalCalculators.js
@@ -0,0 +1,157 @@
+import {roundDigits} from "../Common";
+import React from "react";
+import {Header} from "../Theme";
+import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
+import {GridColumn, GridRow} from "semantic-ui-react";
+import {ColoredInput} from "../Apps";
+
+const initialState = {
+ volts: '',
+ amps: '',
+ ohms: '',
+ watts: '',
+ lastUpdated: [],
+};
+
+// Ohms Law functions. These take two values, and return the other two electrical values.
+const voltsAmps = (volts, amps) => {
+ const watts = volts * amps;
+ const ohms = volts / amps;
+ return [roundDigits(watts) || 0, roundDigits(ohms) || 0]
+};
+const voltsOhms = (volts, ohms) => {
+ const amps = volts / ohms;
+ const watts = volts * amps;
+ return [roundDigits(amps) || 0, roundDigits(watts) || 0]
+};
+const ampsOhms = (amps, ohms) => {
+ const volts = amps * ohms;
+ const watts = volts * amps;
+ return [roundDigits(volts) || 0, roundDigits(watts) || 0]
+};
+const voltsWatts = (volts, watts) => {
+ const amps = watts / volts;
+ const ohms = volts / amps;
+ return [roundDigits(amps) || 0, roundDigits(ohms) || 0]
+};
+const ampsWatts = (amps, watts) => {
+ const volts = watts / amps;
+ const ohms = volts / amps;
+ return [roundDigits(volts) || 0, roundDigits(ohms) || 0]
+};
+const wattsOhms = (watts, ohms) => {
+ const volts = Math.sqrt(watts * ohms);
+ const amps = volts / ohms;
+ return [roundDigits(volts), roundDigits(amps)]
+};
+
+function ohmsLawReducer(state, action) {
+ const {type, value} = 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];
+ }
+
+ // Copy new value into it's respective state. Do not allow negative numbers.
+ if (type === 'amps') {
+ amps = Math.abs(value);
+ } else if (type === 'volts') {
+ volts = Math.abs(value);
+ } else if (type === 'ohms') {
+ ohms = Math.abs(value);
+ } else if (type === 'watts') {
+ watts = Math.abs(value);
+ }
+
+ // Sort the updated values, so we don't have to define everything twice.
+ const sortedLastUpdated = [...newLastUpdated].sort().join(',');
+ switch (sortedLastUpdated) {
+ case 'amps,ohms':
+ [volts, watts] = ampsOhms(amps, ohms);
+ break;
+ case 'amps,volts':
+ [watts, ohms] = voltsAmps(volts, amps);
+ break;
+ case 'amps,watts':
+ [volts, ohms] = ampsWatts(amps, watts);
+ break;
+ case 'ohms,volts':
+ [amps, watts] = voltsOhms(volts, ohms);
+ break;
+ case 'ohms,watts':
+ [volts, amps] = wattsOhms(watts, ohms);
+ break;
+ case 'volts,watts':
+ [amps, ohms] = voltsWatts(volts, watts);
+ break;
+ }
+
+ return {volts, amps, ohms, watts, lastUpdated: newLastUpdated};
+}
+
+export function ElectricalCalculator() {
+ // Provides a form which provides 4 inputs (volts, amps, ohms, watts), and calculates the missing values.
+
+ const [state, dispatch] = React.useReducer(ohmsLawReducer, initialState);
+
+ const calculateColor = (name) => {
+ // Recently updated inputs are white, others are grey.
+ return state.lastUpdated.indexOf(name) >= 0 ? null : 'grey';
+ }
+
+ const inputProps = {
+ fluid: true,
+ type: 'number',
+ labelPosition: 'right',
+ onSelect: e => e.target.select(), // Select the contents of the Input when a user selects it.
+ };
+
+ return
+
+
+
+
+ dispatch({type: 'volts', value: e.target.value})}
+ label='Volts'
+ color={calculateColor('volts')}
+ />
+
+
+ dispatch({type: 'amps', value: e.target.value})}
+ label='Amps'
+ color={calculateColor('amps')}
+ />
+
+
+
+
+ dispatch({type: 'ohms', value: e.target.value})}
+ label='Ohms'
+ color={calculateColor('ohms')}
+ />
+
+
+ dispatch({type: 'watts', value: e.target.value})}
+ label='Watts'
+ color={calculateColor('watts')}
+ />
+
+
+
+
+}
diff --git a/app/src/components/calculators/HamCalculators.js b/app/src/components/calculators/HamCalculators.js
new file mode 100644
index 00000000..4b037fe7
--- /dev/null
+++ b/app/src/components/calculators/HamCalculators.js
@@ -0,0 +1,158 @@
+import React from "react";
+import {TableBody, TableCell, TableHeader, TableHeaderCell, TableRow} from "semantic-ui-react";
+import {Header, Table} from "../Theme";
+import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
+import {roundDigits} from "../Common";
+import {ColoredInput} from "../Apps";
+
+function convertFeetToFeetAndInches(decimalFeet) {
+ // Extract the integer part for feet
+ const feet = Math.floor(decimalFeet);
+ // Calculate the remainder in feet and convert it to inches (1 foot = 12 inches)
+ const inches = Math.round((decimalFeet - feet) * 12);
+
+ return [feet, inches];
+}
+
+const defaultMhzInputValue = '144.2';
+
+export function DipoleAntennaCalculator() {
+ const [mhz, setMhz] = React.useState(144.2);
+ const [wave, setWave] = React.useState(2.08);
+ const [mhzInputValue, setMhzInputValue] = React.useState(defaultMhzInputValue);
+ const [waveInputValue, setWaveInputValue] = React.useState(0);
+ const [feet, setFeet] = React.useState(0);
+ const [inches, setInches] = React.useState(0);
+ const [meters, setMeters] = React.useState(0);
+ const [cm, setCM] = React.useState(0);
+ const c = 299792458;
+
+ // Reduce the full wavelength by the multiplier.
+ const lengths = [
+ {name: '½ Antenna Length', multiplier: 0.5},
+ {name: '¼ Antenna Length', multiplier: 0.25},
+ {name: '⅝ Antenna Length', multiplier: 0.625},
+ ];
+
+ React.useEffect(() => {
+ // Calculate full wavelength.
+ const newFeet = 936 / mhz;
+ setFeet(newFeet);
+ setInches(newFeet * 12);
+ setMeters(newFeet * 0.3048);
+ setCM(newFeet * 0.3048 * 100);
+ setWaveInputValue(roundDigits(c / (mhz * 1e6)));
+ }, [mhz]);
+
+ React.useEffect(() => {
+ if (wave && parseFloat(wave) > 0) {
+ setMhzInputValue(roundDigits(c / parseFloat(wave) / 1e6));
+ }
+ }, [wave])
+
+ const mhzStorageKey = 'dipole_mhz';
+ const storageMhzValue = localStorage.getItem(mhzStorageKey);
+ React.useEffect(() => {
+ if (storageMhzValue) {
+ setMhz(parseFloat(storageMhzValue));
+ setMhzInputValue(storageMhzValue);
+ }
+ }, []);
+
+ const handleMhzChange = (e, {value}) => {
+ setMhzInputValue(value);
+ setMhz(value && parseFloat(value) > 0 ? parseFloat(value) : 0);
+ localStorage.setItem(mhzStorageKey, value);
+ }
+
+ const handleWaveChange = (e, {value}) => {
+ setWaveInputValue(value);
+ setWave(value && parseFloat(value) > 0 ? parseFloat(value) : 0);
+ }
+
+ const [mhzSelected, setMhzSelected] = React.useState(true);
+ const handleClick = (e, name) => {
+ if (name === 'mhz') {
+ setMhzSelected(true);
+ } else {
+ setMhzSelected(false);
+ }
+ }
+
+ return <>
+
+
+
+
+
+ handleClick(e, 'mhz')}
+ color={mhzSelected ? null : 'grey'}
+ />
+
+
+ handleClick(e, 'wave')}
+ color={mhzSelected ? 'grey' : null}
+ />
+
+
+
+
+ {lengths.map(i => {
+ const [feet_, inches_] = convertFeetToFeetAndInches(feet * i.multiplier);
+ const [half_feet, half_inches] = convertFeetToFeetAndInches(feet * i.multiplier / 2);
+ return
+
+
+ {i.name}
+ Leg Length
+
+
+
+
+
+
+ {feet_} ft. {inches_} in.
+
+
+ {half_feet} ft. {half_inches} in.
+
+
+
+
+ {roundDigits(inches * i.multiplier)} inches
+
+
+ {roundDigits(inches * i.multiplier / 2)} inches
+
+
+
+
+ {roundDigits(meters * i.multiplier)} meters
+
+
+ {roundDigits(meters * i.multiplier / 2)} meters
+
+
+
+
+ {roundDigits(cm * i.multiplier)} cm.
+
+
+ {roundDigits(cm * i.multiplier / 2)} cm.
+
+
+
+
+ })}
+ >
+}
\ No newline at end of file
diff --git a/app/src/components/calculators/TemperatureCalculator.js b/app/src/components/calculators/TemperatureCalculator.js
new file mode 100644
index 00000000..68ce4bac
--- /dev/null
+++ b/app/src/components/calculators/TemperatureCalculator.js
@@ -0,0 +1,70 @@
+import React from "react";
+import {roundDigits} from "../Common";
+import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
+import {GridColumn, GridRow, Input} from "semantic-ui-react";
+
+export function TemperatureCalculator() {
+ const [state, setState] = React.useState({
+ celsius: '',
+ fahrenheit: '',
+ kelvin: '',
+ })
+
+ const handleInputChange = (e, {name, value}) => {
+ if (value === '') {
+ // User has cleared the input;
+ setState({volt: '', ohm: '', amp: ''});
+ return
+ }
+
+ const numValue = parseFloat(value.endsWith('.') ? `${value}0` : value);
+ console.debug(`Calculating ${value} ${name}`);
+ let newCelsius;
+ let newKelvin;
+ let newFahrenheit;
+ if (name === 'fahrenheit') {
+ newFahrenheit = value;
+ newCelsius = roundDigits((numValue - 32) / 1.8);
+ newKelvin = roundDigits(newCelsius + 273.15);
+ } else if (name === 'celsius') {
+ newCelsius = value;
+ newFahrenheit = roundDigits((numValue * 1.8) + 32);
+ newKelvin = roundDigits(numValue + 273.15);
+ } else if (name === 'kelvin') {
+ newKelvin = value;
+ newCelsius = roundDigits(numValue - 273.15);
+ newFahrenheit = roundDigits((newCelsius * 1.8) + 32);
+ }
+ setState({
+ celsius: newCelsius,
+ fahrenheit: newFahrenheit,
+ kelvin: newKelvin
+ });
+ }
+
+ const handleClick = (e) => {
+ e.target.select();
+ }
+
+ const commonInputProps = {
+ fluid: true,
+ labelPosition: 'right',
+ type: 'number',
+ onChange: handleInputChange,
+ onClick: handleClick
+ };
+
+ return
+
+
+
+
+
+
+
+
+
+
+
+
+}