diff --git a/app/src/Events.js b/app/src/Events.js
index 0aa66f35..e25ec03d 100644
--- a/app/src/Events.js
+++ b/app/src/Events.js
@@ -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';
diff --git a/app/src/components/Channels.js b/app/src/components/Channels.js
index cf0a4790..3cb97e10 100644
--- a/app/src/components/Channels.js
+++ b/app/src/components/Channels.js
@@ -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 ;
}
@@ -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}
>
@@ -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
{tagEditModal}
diff --git a/app/src/components/Common.js b/app/src/components/Common.js
index c97994ad..0b44ef48 100644
--- a/app/src/components/Common.js
+++ b/app/src/components/Common.js
@@ -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));
-}
\ No newline at end of file
+}
+
+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];
+}
diff --git a/app/src/components/Nav.js b/app/src/components/Nav.js
index b8396c44..c1cf6fb3 100644
--- a/app/src/components/Nav.js
+++ b/app/src/components/Nav.js
@@ -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 {props.children}
}
+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 =
;
const name = {NAME || wrolpiIcon};
const topNavText = wrolModeEnabled ? <>{name} > : name;
const {i} = React.useContext(ThemeContext);
- const navColor = settings && settings.nav_color ? settings.nav_color : 'violet';
const homeLink =
{topNavText}
diff --git a/app/src/components/calculators/ElectricalCalculators.js b/app/src/components/calculators/ElectricalCalculators.js
index c09cc3be..c6477734 100644
--- a/app/src/components/calculators/ElectricalCalculators.js
+++ b/app/src/components/calculators/ElectricalCalculators.js
@@ -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
-
+
}
diff --git a/app/src/components/calculators/TemperatureCalculator.js b/app/src/components/calculators/TemperatureCalculator.js
index 53435653..979031bb 100644
--- a/app/src/components/calculators/TemperatureCalculator.js
+++ b/app/src/components/calculators/TemperatureCalculator.js
@@ -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
}
diff --git a/app/src/hooks/customHooks.js b/app/src/hooks/customHooks.js
index 17ed4553..729369f2 100644
--- a/app/src/hooks/customHooks.js
+++ b/app/src/hooks/customHooks.js
@@ -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 = () => {