From 9986ce8a1f62f67ae8426e9be55579f0a145ceb6 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 20 Jun 2024 09:51:29 -0600 Subject: [PATCH] Created Dipole Antenna calculator. --- app/src/components/Calculators.js | 226 +----------------- app/src/components/Search.js | 5 + .../calculators/ElectricalCalculators.js | 157 ++++++++++++ .../components/calculators/HamCalculators.js | 158 ++++++++++++ .../calculators/TemperatureCalculator.js | 70 ++++++ 5 files changed, 395 insertions(+), 221 deletions(-) create mode 100644 app/src/components/calculators/ElectricalCalculators.js create mode 100644 app/src/components/calculators/HamCalculators.js create mode 100644 app/src/components/calculators/TemperatureCalculator.js 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
-
Ohm's Law
- - - - 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
+
Ohm's Law
+ + + + 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 <> +
Dipole Antenna
+ + + + + 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 + + + + + + + + + + + + +}