mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Creating Calculators in app. Current calculators are Temperature and Electrical.
This commit is contained in:
parent
279fe2af3e
commit
2aebb7c406
6 changed files with 293 additions and 4 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 <PageContainer>
|
||||
<Routes>
|
||||
<Route path='calculators' element={<CalculatorsPage/>}/>
|
||||
<Route path='otp' exact element={<OTP/>}/>
|
||||
<Route path='statistics' exact element={<StatisticsPage/>}/>
|
||||
<Route path='vin' exact element={<VINDecoder/>}/>
|
||||
</Routes>
|
||||
</PageContainer>
|
||||
}
|
||||
|
||||
export function ColoredInput({name, value, label, color, ...props}) {
|
||||
label = label ? <Label color={color}>{label}</Label> : null;
|
||||
return <Input value={value} name={name} label={label} {...props}/>
|
||||
}
|
||||
262
app/src/components/Calculators.js
Normal file
262
app/src/components/Calculators.js
Normal file
|
|
@ -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 <Grid columns={3}>
|
||||
<GridRow>
|
||||
<GridColumn>
|
||||
<Input value={state.celsius} label='°C' name='celsius' {...commonInputProps}/>
|
||||
</GridColumn>
|
||||
<GridColumn>
|
||||
<Input value={state.fahrenheit} label='°F' name='fahrenheit' {...commonInputProps}/>
|
||||
</GridColumn>
|
||||
<GridColumn>
|
||||
<Input value={state.kelvin} label='K' name='kelvin' {...commonInputProps}/>
|
||||
</GridColumn>
|
||||
</GridRow>
|
||||
</Grid>
|
||||
}
|
||||
|
||||
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 <div>
|
||||
<Header as='h4'>Ohm's Law</Header>
|
||||
<Grid columns={2}>
|
||||
<GridRow>
|
||||
<GridColumn>
|
||||
<ColoredInput {...inputProps}
|
||||
value={state.volts}
|
||||
onChange={e => dispatch({type: 'volts', value: e.target.value})}
|
||||
label='Volts'
|
||||
color={calculateColor('volts')}
|
||||
/>
|
||||
</GridColumn>
|
||||
<GridColumn>
|
||||
<ColoredInput {...inputProps}
|
||||
value={state.amps}
|
||||
onChange={e => dispatch({type: 'amps', value: e.target.value})}
|
||||
label='Amps'
|
||||
color={calculateColor('amps')}
|
||||
/>
|
||||
</GridColumn>
|
||||
</GridRow>
|
||||
<GridRow>
|
||||
<GridColumn>
|
||||
<ColoredInput {...inputProps}
|
||||
value={state.ohms}
|
||||
onChange={e => dispatch({type: 'ohms', value: e.target.value})}
|
||||
label='Ohms'
|
||||
color={calculateColor('ohms')}
|
||||
/>
|
||||
</GridColumn>
|
||||
<GridColumn>
|
||||
<ColoredInput {...inputProps}
|
||||
value={state.watts}
|
||||
onChange={e => dispatch({type: 'watts', value: e.target.value})}
|
||||
label='Watts'
|
||||
color={calculateColor('watts')}
|
||||
/>
|
||||
</GridColumn>
|
||||
</GridRow>
|
||||
</Grid>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function CalculatorsPage() {
|
||||
const {calc, setCalc} = useCalcQuery();
|
||||
|
||||
const calculators = [
|
||||
{key: 'temperature', icon: 'thermometer', button: 'Temperature', contents: <TemperatureCalculator/>},
|
||||
{key: 'electrical', icon: 'lightning', button: 'Electrical', contents: <ElectricalCalculator/>},
|
||||
];
|
||||
|
||||
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 <Button key={key} onClick={() => setCalc(i.key)}>
|
||||
{icon && <Icon name={i.icon}/>}
|
||||
{button}
|
||||
</Button>
|
||||
});
|
||||
|
||||
const body = activeCalculator ? <>
|
||||
<BackButton style={{marginBottom: '1em'}}/>
|
||||
<span>{contents}</span>
|
||||
</>
|
||||
: calculatorButtons;
|
||||
|
||||
// TODO add Breadcrumb here.
|
||||
return <>
|
||||
<Header>{header}</Header>
|
||||
{body}
|
||||
</>
|
||||
}
|
||||
|
|
@ -1255,9 +1255,9 @@ export function DirectoryInput({disabled, error, placeholder, setInput, value, r
|
|||
</div>;
|
||||
}
|
||||
|
||||
export const BackButton = () => {
|
||||
export const BackButton = ({...props}) => {
|
||||
const navigate = useNavigate();
|
||||
return <Button icon='arrow left' content='Back' onClick={() => navigate(-1)}/>;
|
||||
return <Button icon='arrow left' content='Back' onClick={() => navigate(-1)} {...props}/>;
|
||||
}
|
||||
|
||||
export const ColorToSemanticHexColor = (color) => {
|
||||
|
|
@ -1715,4 +1715,8 @@ export function IframeViewer({title, viewerUrl}) {
|
|||
}}/>
|
||||
</Media>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export function roundDigits(value, decimals = 2) {
|
||||
return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const links = [
|
|||
{text: 'Inventory', to: '/inventory', key: 'inventory'},
|
||||
{to: '/more/otp', text: 'One Time Pad', end: true},
|
||||
{to: '/more/vin', text: 'VIN Decoder', end: true},
|
||||
{to: '/more/calculators', text: 'Calculators', end: true},
|
||||
{to: '/more/statistics', text: 'Statistics', end: true},
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1186,3 +1186,8 @@ export const useDockerized = () => {
|
|||
const {status} = React.useContext(StatusContext);
|
||||
return status && status['dockerized'] === true;
|
||||
}
|
||||
|
||||
export const useCalcQuery = () => {
|
||||
const [calc, setCalc] = useOneQuery('calc');
|
||||
return {calc, setCalc}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue