Using localStorage to store navbar color and calculator values.

This commit is contained in:
Roland 2024-10-05 14:06:13 -06:00
parent 11dc370ed9
commit b643728b96
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
7 changed files with 81 additions and 53 deletions

View file

@ -2,6 +2,7 @@ import React, {useEffect} from 'react';
import {useRecurringTimeout} from "./hooks/customHooks";
import {getEvents} from "./api";
import {toast} from "react-semantic-toasts-2";
import {useLocalStorage} from "./components/Common";
const apiEventName = 'apiEvent';

View file

@ -88,7 +88,6 @@ function ChannelStatistics({statistics}) {
function ChannelPage({create, header}) {
const [disabled, setDisabled] = useState(false);
const [errors, setErrors] = useState({});
const [error, setError] = useState(false);
const [success, setSuccess] = useState(false);
const [messageHeader, setMessageHeader] = useState();
@ -97,24 +96,22 @@ function ChannelPage({create, header}) {
const navigate = useNavigate();
const {channelId} = useParams();
const {channel, changeValue, fetchChannel} = useChannel(channelId);
const {channel, ready: channelReady, changeValue, fetchChannel} = useChannel(channelId);
const {SingleTag} = React.useContext(TagsContext);
const [tagEditModalOpen, setTagEditModalOpen] = useState(false);
const [newTagName, setNewTagName] = useState(channel ? channel.tag_name : null);
const [newTagName, setNewTagName] = useState(null);
const [moveToTagDirectory, setMoveToTagDirectory] = useState(true);
const [newTagDirectory, setNewTagDirectory] = useState('');
useTitle(_.isEmpty(channel) ? null : `${channel.name} Channel`);
React.useEffect(() => {
console.debug(channel);
if (!_.isEmpty(channel)) {
setNewTagName(channel.tag_name);
}
}, [JSON.stringify(channel)]);
setNewTagName(channel.tag_name);
}, [channel.tag_name]);
if (_.isEmpty(channel)) {
if (!create && !channelReady) {
// Waiting for editing Channel to be fetched.
return <Loader active/>;
}
@ -147,7 +144,6 @@ function ChannelPage({create, header}) {
name: channel.name,
directory: channel.directory,
url: channel.url,
tag_name: channel.tag_name,
};
setDisabled(true);
@ -155,6 +151,8 @@ function ChannelPage({create, header}) {
let response = null;
try {
if (create !== undefined) {
// Can create a Channel with a Tag.
body.tag_name = channel.tag_name;
response = await createChannel(body);
} else {
response = await updateChannel(channelId, body);
@ -172,7 +170,7 @@ function ChannelPage({create, header}) {
setDisabled(false);
}
if (response && response.status >= 200 && response.status < 300) {
if (response && response.ok) {
let location = response.headers.get('Location');
let channelResponse = await fetch(location);
let data = await channelResponse.json();
@ -198,13 +196,7 @@ function ChannelPage({create, header}) {
// Some error occurred.
let error = await response.json();
let cause = error.cause;
if (error.code === 'UNKNOWN_DIRECTORY' || (cause && cause.code === 'UNKNOWN_DIRECTORY')) {
setErrorMessage(
'Invalid directory',
'This directory does not exist.',
'directory',
);
} else if (cause && cause.code === 'CHANNEL_DIRECTORY_CONFLICT') {
if (cause && cause.code === 'CHANNEL_DIRECTORY_CONFLICT') {
setErrorMessage(
'Invalid directory',
'This directory is already used by another channel',
@ -249,13 +241,6 @@ function ChannelPage({create, header}) {
setDownloadModalOpen(false);
}
const handleTagModalOpen = (e) => {
if (e) {
e.preventDefault();
}
setTagEditModalOpen(true);
}
const handleTagEditChannel = async () => {
try {
await tagChannel(channelId, newTagName, moveToTagDirectory ? newTagDirectory : null);
@ -348,7 +333,6 @@ function ChannelPage({create, header}) {
error={error}
success={success}
autoComplete="off"
onSubmit={handleSubmit}
>
<FormGroup>
<FormField width={8}>
@ -358,7 +342,6 @@ function ChannelPage({create, header}) {
type="text"
placeholder="Short Channel Name"
disabled={disabled}
error={errors.name}
value={channel.name}
onChange={(e, {value}) => changeValue('name', value)}
/>
@ -382,7 +365,6 @@ function ChannelPage({create, header}) {
name="url"
type="url"
placeholder='https://example.com/channel/videos'
error={errors.url}
value={channel.url}
onChange={handleInputChange}
/>
@ -410,6 +392,7 @@ function ChannelPage({create, header}) {
size='small'
confirmContent='Are you sure you want to delete this channel? No video files will be deleted.'
confirmButton='Delete'
confirmHeader='Delete Channel?'
onClick={handleDelete}
obeyWROLMode={true}
style={{marginTop: '1em'}}
@ -423,7 +406,7 @@ function ChannelPage({create, header}) {
>Refresh</APIButton>
<Button
size='small'
onClick={handleTagModalOpen}
onClick={() => setTagEditModalOpen(true)}
color='green'
>Tag</Button>
{tagEditModal}

View file

@ -1451,6 +1451,7 @@ export function useAPIButton(
disabled,
confirmContent,
confirmButton,
confirmHeader,
themed = true,
obeyWROLMode = false,
icon = null,
@ -1565,6 +1566,7 @@ export function useAPIButton(
onCancel={() => setConfirmOpen(false)}
onConfirm={localOnConfirm}
confirmButton={confirmButton}
header={confirmHeader}
/>
</>
}
@ -1580,6 +1582,7 @@ export function APIButton({
disabled,
confirmContent,
confirmButton,
confirmHeader,
themed,
obeyWROLMode,
icon,
@ -1593,6 +1596,7 @@ export function APIButton({
disabled,
confirmContent,
confirmButton,
confirmHeader,
themed,
obeyWROLMode,
icon,
@ -1810,4 +1814,38 @@ const validUrlRegex = /^(http|https):\/\/[^ "]+$/;
export function validURL(url) {
return !(url && !validUrlRegex.test(url));
}
}
export function useLocalStorage(key, initialValue, decode = JSON.parse, encode = JSON.stringify) {
// Use localstorage to store some JSON encode-able.
// Initialize state with the value from localStorage or initial value
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
// Parse the stored item (integer, bool, etc.). Use the initial value if empty.
return item ? decode(item) : initialValue;
} catch (error) {
console.error('Error reading localStorage:', error);
return initialValue;
}
});
// Save to localStorage when the value changes
useEffect(() => {
try {
window.localStorage.setItem(key, encode(value));
} catch (error) {
console.error('Error setting localStorage:', error);
}
}, [key, value, encode]);
// Return the stored value and a function to update it
return [value, setValue];
}
export function useLocalStorageInt(key, initialValue) {
// Use localStorage to store an integer.
const [storedValue, setStoredValue] = useLocalStorage(key, initialValue, parseInt, (num) => num.toString());
return [storedValue, setStoredValue];
}

View file

@ -2,7 +2,7 @@ import React from "react";
import {Link, NavLink} from "react-router-dom";
import {Dropdown, Icon as SIcon, Menu} from "semantic-ui-react";
import {Media, SettingsContext, StatusContext, ThemeContext} from "../contexts/contexts";
import {CPUTemperatureIcon, DarkModeToggle, HotspotStatusIcon, NAME, SystemLoadIcon} from "./Common";
import {CPUTemperatureIcon, DarkModeToggle, HotspotStatusIcon, NAME, SystemLoadIcon, useLocalStorage} from "./Common";
import {ShareButton} from "./Share";
import {useWROLMode} from "../hooks/customHooks";
import {SearchIconButton} from "./Search";
@ -59,15 +59,28 @@ function NavIconWrapper(props) {
return <div style={{margin: '0.8em'}}>{props.children}</div>
}
function useNavColorSetting() {
// Use localstorage to avoid flickering navbar color on startup.
const {settings} = React.useContext(SettingsContext);
const [navColor, setNavColor] = useLocalStorage('nav_color', 'violet');
React.useEffect(() => {
const newColor = settings.nav_color || 'violet';
setNavColor(newColor);
localStorage.setItem('nav_color', newColor);
}, [settings.nav_color]);
return navColor
}
export function NavBar() {
const wrolModeEnabled = useWROLMode();
const {status} = React.useContext(StatusContext);
const {settings} = React.useContext(SettingsContext);
const navColor = useNavColorSetting();
const wrolpiIcon = <img src='/icon.svg' height='32px' width='32px' alt='WROLPi Home Icon'/>;
const name = <i>{NAME || wrolpiIcon}</i>;
const topNavText = wrolModeEnabled ? <>{name}&nbsp; <SIcon name='lock'/></> : name;
const {i} = React.useContext(ThemeContext);
const navColor = settings && settings.nav_color ? settings.nav_color : 'violet';
const homeLink = <NavLink className='item' to='/' style={{paddingTop: 0, paddingBottom: 0}}>
{topNavText}

View file

@ -1,4 +1,4 @@
import {HelpPopup, roundDigits, Toggle} from "../Common";
import {HelpPopup, roundDigits, Toggle, useLocalStorage, useLocalStorageInt} from "../Common";
import React, {useState} from "react";
import {Form, Header, Segment, Table} from "../Theme";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
@ -264,23 +264,15 @@ export const calcPowerLossPercent = (isSAE, volts, amps, resistancePerKiloLength
return (voltageDrop / volts) * 100; // Return as a percentage
};
const PowerLossCalculator = ({defaultVolts = 120}) => {
const [volts, setVolts] = useState(defaultVolts);
const [wireType, setWireType] = useState('solid');
const [isSAE, setIsSAE] = useState(true);
const [length, setLength] = useState(100);
const PowerLossCalculator = ({volts, setVolts}) => {
const [wireType, setWireType] = useLocalStorage('calculators.power_loss.wire_type', 'solid');
const [isSAE, setIsSAE] = useLocalStorage('calculators.power_loss.is_sae', true);
const [length, setLength] = useLocalStorageInt('calculators.power_loss.length', 100);
const [ampsRange] = useState([1, 5, 10, 20, 40, 100]);
const {inverted} = React.useContext(ThemeContext);
const warningBackgroundColor = inverted === 'inverted' ? '#332020' : '#ffbebe';
React.useEffect(() => {
// Copy voltage from Ohm's Law calculator.
if (defaultVolts > 0) {
setVolts(defaultVolts);
}
}, [defaultVolts]);
const resistances = isSAE ? resistancesPerKFeet[wireType]
: resistancesPerKm[wireType];
@ -385,9 +377,9 @@ const PowerLossCalculator = ({defaultVolts = 120}) => {
};
export const ElectricalCalculators = () => {
const [volts, setVolts] = React.useState();
const [volts, setVolts] = useLocalStorageInt('calculators.electrical.volts', 120);
return <Container>
<Segment><OhmsLawCalculator setVolts={setVolts}/></Segment>
<Segment><PowerLossCalculator defaultVolts={volts}/></Segment>
<Segment><PowerLossCalculator volts={volts} setVolts={setVolts}/></Segment>
</Container>
}

View file

@ -13,7 +13,7 @@ export function TemperatureCalculator() {
const handleInputChange = (e, {name, value}) => {
if (value === '') {
// User has cleared the input;
setState({volt: '', ohm: '', amp: ''});
setState({celsius: '', fahrenheit: '', kelvin: ''});
return
}

View file

@ -428,7 +428,7 @@ export const useChannel = (channel_id) => {
match_regex: '',
tag_name: null,
};
const [fetched, setFetched] = useState(false);
const [ready, setReady] = useState(false);
const [channel, setChannel] = useState(emptyChannel);
const [original, setOriginal] = useState({});
@ -442,8 +442,9 @@ export const useChannel = (channel_id) => {
c['match_regex'] = c['match_regex'] || '';
setChannel(c);
setOriginal(c);
setFetched(true);
setReady(true);
} catch (e) {
setReady(false);
console.error(e);
toast({
type: 'error',
@ -459,7 +460,7 @@ export const useChannel = (channel_id) => {
useEffect(() => {
localGetChannel();
}, [channel_id])
}, [channel_id]);
const changeValue = (name, value) => {
const newChannel = {...channel, [name]: value};
@ -467,7 +468,7 @@ export const useChannel = (channel_id) => {
setChannel(newChannel);
}
return {channel, changeValue, original, fetched, fetchChannel: localGetChannel};
return {channel, changeValue, original, ready, fetchChannel: localGetChannel};
}
export const useChannels = () => {