From 2aebb7c4060b6278d46a431fc2d85dcfbd7371b1 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 31 May 2024 23:53:30 -0600 Subject: [PATCH] Creating Calculators in app. Current calculators are Temperature and Electrical. --- app/src/App.js | 1 + app/src/components/Apps.js | 18 +- app/src/components/Calculators.js | 262 ++++++++++++++++++++++++++++++ app/src/components/Common.js | 10 +- app/src/components/Nav.js | 1 + app/src/hooks/customHooks.js | 5 + 6 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 app/src/components/Calculators.js diff --git a/app/src/App.js b/app/src/App.js index 12cbc493..343d0de5 100644 --- a/app/src/App.js +++ b/app/src/App.js @@ -22,6 +22,7 @@ import {FilePreviewProvider} from "./components/FilePreview"; import {TagsProvider} from "./Tags"; import {ZimRoute} from "./components/Zim"; import {IframeViewer} from "./components/Common"; +import {CalculatorsRoute} from "./components/Calculators"; function PageNotFound() { const {t} = useContext(ThemeContext); diff --git a/app/src/components/Apps.js b/app/src/components/Apps.js index 289b591f..3791d9f6 100644 --- a/app/src/components/Apps.js +++ b/app/src/components/Apps.js @@ -1,5 +1,14 @@ import React, {useContext} from 'react'; -import {Divider, Input, SegmentGroup, StatisticLabel, StatisticValue, TableCell, TableRow} from "semantic-ui-react"; +import { + Divider, + Input, + Label, + SegmentGroup, + StatisticLabel, + StatisticValue, + TableCell, + TableRow +} from "semantic-ui-react"; import {Link, Route, Routes} from "react-router-dom"; import {decryptOTP, encryptOTP} from "../api"; import { @@ -15,6 +24,7 @@ import { import {ThemeContext} from "../contexts/contexts"; import {Button, Header, Loader, Segment, Statistic, StatisticGroup, Table, TextArea} from "./Theme"; import {useStatistics, useVINDecoder} from "../hooks/customHooks"; +import {CalculatorsPage} from "./Calculators"; class Encrypt extends React.Component { constructor(props) { @@ -332,9 +342,15 @@ function StatisticsPage() { export function MoreRoute(props) { return + }/> }/> }/> }/> } + +export function ColoredInput({name, value, label, color, ...props}) { + label = label ? : null; + return +} \ No newline at end of file diff --git a/app/src/components/Calculators.js b/app/src/components/Calculators.js new file mode 100644 index 00000000..1e71a60f --- /dev/null +++ b/app/src/components/Calculators.js @@ -0,0 +1,262 @@ +import React from "react"; +import {BackButton, roundDigits, 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')} + /> + + + +
+} + +export function CalculatorsPage() { + const {calc, setCalc} = useCalcQuery(); + + const calculators = [ + {key: 'temperature', icon: 'thermometer', button: 'Temperature', contents: }, + {key: 'electrical', icon: 'lightning', button: 'Electrical', contents: }, + ]; + + const activeCalculator = calc ? + calculators.filter(i => i.key === calc)[0] + : null; + const {button, title, contents} = activeCalculator || {}; + + const name = title || button + ' Calculator'; + const header = activeCalculator ? name : 'Calculators'; + useTitle(header); + + const calculatorButtons = calculators.map(i => { + const {button, icon, key} = i; + return + }); + + const body = activeCalculator ? <> + + {contents} + + : calculatorButtons; + + // TODO add Breadcrumb here. + return <> +
{header}
+ {body} + +} diff --git a/app/src/components/Common.js b/app/src/components/Common.js index 320fb1e0..ba515c06 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -1255,9 +1255,9 @@ export function DirectoryInput({disabled, error, placeholder, setInput, value, r ; } -export const BackButton = () => { +export const BackButton = ({...props}) => { const navigate = useNavigate(); - return