fix(settings): preserve speed limiter units

Persist the selected display unit for disabled limits, preserve in-progress fractional input, and keep exact KiB-backed preset values visible.\n\nNo linked issue.
This commit is contained in:
NimBold
2026-07-15 09:19:19 +03:30
parent b81e8391e1
commit c34c489aef
6 changed files with 122 additions and 26 deletions
+6
View File
@@ -2,6 +2,10 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use ts_rs::TS; use ts_rs::TS;
fn default_speed_limit_unit() -> String {
"MB/s".to_string()
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, TS)] #[derive(Clone, Copy, Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
#[ts(export, export_to = "../../src/bindings/")] #[ts(export, export_to = "../../src/bindings/")]
@@ -264,6 +268,8 @@ pub struct PersistedSettings {
pub scheduler_last_start_key: String, pub scheduler_last_start_key: String,
pub scheduler_last_stop_key: String, pub scheduler_last_stop_key: String,
pub last_custom_speed_limit_ki_b: u32, pub last_custom_speed_limit_ki_b: u32,
#[serde(default = "default_speed_limit_unit")]
pub last_custom_speed_limit_unit: String,
pub per_server_connections: i32, pub per_server_connections: i32,
pub max_automatic_retries: i32, pub max_automatic_retries: i32,
pub show_notifications: bool, pub show_notifications: bool,
+8
View File
@@ -266,6 +266,12 @@ fn validate_settings(settings: &mut PersistedSettings) {
settings.max_concurrent_downloads = settings.max_concurrent_downloads.min(12); settings.max_concurrent_downloads = settings.max_concurrent_downloads.min(12);
settings.per_server_connections = settings.per_server_connections.clamp(1, 16); settings.per_server_connections = settings.per_server_connections.clamp(1, 16);
settings.max_automatic_retries = settings.max_automatic_retries.clamp(0, 10); settings.max_automatic_retries = settings.max_automatic_retries.clamp(0, 10);
if !matches!(
settings.last_custom_speed_limit_unit.as_str(),
"KB/s" | "MB/s"
) {
settings.last_custom_speed_limit_unit = default_settings().last_custom_speed_limit_unit;
}
} }
fn default_category_subfolders() -> HashMap<String, String> { fn default_category_subfolders() -> HashMap<String, String> {
@@ -430,6 +436,7 @@ fn default_settings() -> PersistedSettings {
scheduler_last_start_key: String::new(), scheduler_last_start_key: String::new(),
scheduler_last_stop_key: String::new(), scheduler_last_stop_key: String::new(),
last_custom_speed_limit_ki_b: 1024, last_custom_speed_limit_ki_b: 1024,
last_custom_speed_limit_unit: "MB/s".to_string(),
per_server_connections: 16, per_server_connections: 16,
max_automatic_retries: 3, max_automatic_retries: 3,
show_notifications: true, show_notifications: true,
@@ -532,6 +539,7 @@ mod tests {
assert_eq!(settings.max_concurrent_downloads, 5); assert_eq!(settings.max_concurrent_downloads, 5);
assert_eq!(settings.global_speed_limit, "512K"); assert_eq!(settings.global_speed_limit, "512K");
assert_eq!(settings.last_custom_speed_limit_unit, "MB/s");
assert_eq!(settings.speed_limit_preset_values, vec![1.0, 5.0, 10.0]); assert_eq!(settings.speed_limit_preset_values, vec![1.0, 5.0, 10.0]);
assert!(!settings.logs_enabled); assert!(!settings.logs_enabled);
assert!(!settings.scheduler.enabled); assert!(!settings.scheduler.enabled);
+1 -1
View File
@@ -8,4 +8,4 @@ import type { SettingsTab } from "./SettingsTab";
import type { SiteLogin } from "./SiteLogin"; import type { SiteLogin } from "./SiteLogin";
import type { Theme } from "./Theme"; import type { Theme } from "./Theme";
export type PersistedSettings = { theme: Theme, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array<number>, logsEnabled: boolean, isSidebarVisible: boolean, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, }; export type PersistedSettings = { theme: Theme, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array<string>, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array<number>, logsEnabled: boolean, isSidebarVisible: boolean, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array<string>, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, lastCustomSpeedLimitUnit: string, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array<SiteLogin>, autoCheckUpdates: boolean, keychainAccessGranted: boolean, };
+33
View File
@@ -1,7 +1,11 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { import {
convertSpeedValue, convertSpeedValue,
clampSpeedDisplayValue,
displayValueFromPresetBase, displayValueFromPresetBase,
formatSpeedLimitForStorage,
formatPresetValue,
parseLimit,
presetBaseFromDisplayValue, presetBaseFromDisplayValue,
} from './SpeedLimiterView'; } from './SpeedLimiterView';
@@ -20,4 +24,33 @@ describe('SpeedLimiterView speed conversions', () => {
expect(convertSpeedValue(1500, 'KB/s', 'MB/s')).toBe(1500 / 1024); expect(convertSpeedValue(1500, 'KB/s', 'MB/s')).toBe(1500 / 1024);
expect(convertSpeedValue(convertSpeedValue(1500, 'KB/s', 'MB/s'), 'MB/s', 'KB/s')).toBe(1500); expect(convertSpeedValue(convertSpeedValue(1500, 'KB/s', 'MB/s'), 'MB/s', 'KB/s')).toBe(1500);
}); });
it('preserves the selected unit when saving a non-MiB-aligned MB/s value', () => {
const storedLimit = formatSpeedLimitForStorage(1.5, 'MB/s');
expect(storedLimit).toBe('1.5M');
expect(parseLimit(storedLimit, 1024)).toEqual({ value: 1.5, unit: 'MB/s' });
});
it('preserves an explicitly selected KB/s unit at MiB boundaries', () => {
const storedLimit = formatSpeedLimitForStorage(1024, 'KB/s');
expect(storedLimit).toBe('1024K');
expect(parseLimit(storedLimit, 1)).toEqual({ value: 1024, unit: 'KB/s' });
});
it('restores the persisted unit while the limiter is disabled', () => {
expect(parseLimit('', 1536, 'MB/s')).toEqual({ value: 1.5, unit: 'MB/s' });
expect(parseLimit('', 1536, 'KB/s')).toEqual({ value: 1536, unit: 'KB/s' });
});
it('allows sub-one MB/s values while enforcing the 1 KiB backend minimum', () => {
expect(clampSpeedDisplayValue(0.5, 'MB/s')).toBe(0.5);
expect(clampSpeedDisplayValue(0, 'MB/s')).toBe(1 / 1024);
expect(clampSpeedDisplayValue(0, 'KB/s')).toBe(1);
});
it('shows the exact stored preset value instead of a rounded label', () => {
expect(formatPresetValue(1.46484375)).toBe('1.46484375');
});
}); });
+65 -25
View File
@@ -25,17 +25,48 @@ export function convertSpeedValue(value: number, fromUnit: SpeedUnit, toUnit: Sp
return speedValueFromKiB(speedValueToKiB(value, fromUnit), toUnit); return speedValueFromKiB(speedValueToKiB(value, fromUnit), toUnit);
} }
export function parseLimit(limit: string, fallback: number): { value: number; unit: SpeedUnit } { export function parseLimit(
limit: string,
fallback: number,
fallbackUnit: SpeedUnit = 'MB/s'
): { value: number; unit: SpeedUnit } {
const match = limit.trim().match(/^(\d+(?:\.\d+)?)\s*([km]?)b?(?:\/s)?$/i); const match = limit.trim().match(/^(\d+(?:\.\d+)?)\s*([km]?)b?(?:\/s)?$/i);
const suffix = match?.[2].toLowerCase();
const valueKiB = match const valueKiB = match
? speedValueToKiB(Number(match[1]) * (match[2].toLowerCase() === 'm' ? KIB_PER_MIB : 1), 'KB/s') ? speedValueToKiB(Number(match[1]) * (suffix === 'm' ? KIB_PER_MIB : 1), 'KB/s')
: speedValueToKiB(fallback, 'KB/s'); : speedValueToKiB(fallback, 'KB/s');
if (!match) {
return { value: speedValueFromKiB(valueKiB, fallbackUnit), unit: fallbackUnit };
}
if (suffix === 'm') {
return { value: speedValueFromKiB(valueKiB, 'MB/s'), unit: 'MB/s' };
}
if (suffix === 'k') {
return { value: valueKiB, unit: 'KB/s' };
}
return valueKiB >= 1024 && valueKiB % 1024 === 0 return valueKiB >= 1024 && valueKiB % 1024 === 0
? { value: valueKiB / 1024, unit: 'MB/s' } ? { value: valueKiB / 1024, unit: 'MB/s' }
: { value: valueKiB, unit: 'KB/s' }; : { value: valueKiB, unit: 'KB/s' };
} }
export function formatSpeedLimitForStorage(value: number, unit: SpeedUnit): string {
const valueKiB = speedValueToKiB(value, unit);
return unit === 'MB/s'
? `${speedValueFromKiB(valueKiB, 'MB/s')}M`
: `${valueKiB}K`;
}
export function clampSpeedDisplayValue(value: number, unit: SpeedUnit): number {
const numericValue = Number.isFinite(value) ? value : speedValueFromKiB(1, unit);
const minimum = speedValueFromKiB(1, unit);
const maximum = speedValueFromKiB(MAX_LIMIT_KIB, unit);
return Math.max(minimum, Math.min(maximum, numericValue));
}
function sanitizePresetValues(values: number[]): number[] { function sanitizePresetValues(values: number[]): number[] {
const cleaned = values const cleaned = values
.map(value => Number(value)) .map(value => Number(value))
@@ -53,22 +84,25 @@ export function displayValueFromPresetBase(value: number, unit: SpeedUnit): numb
return speedValueFromKiB(speedValueToKiB(value, 'MB/s'), unit); return speedValueFromKiB(speedValueToKiB(value, 'MB/s'), unit);
} }
function formatPresetValue(value: number): string { export function formatPresetValue(value: number): string {
return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/\.?0+$/, ''); return String(value);
} }
export default function SpeedLimiterView() { export default function SpeedLimiterView() {
const globalSpeedLimit = useSettingsStore(state => state.globalSpeedLimit); const globalSpeedLimit = useSettingsStore(state => state.globalSpeedLimit);
const lastCustomSpeedLimitKiB = useSettingsStore(state => state.lastCustomSpeedLimitKiB); const lastCustomSpeedLimitKiB = useSettingsStore(state => state.lastCustomSpeedLimitKiB);
const lastCustomSpeedLimitUnit = useSettingsStore(state => state.lastCustomSpeedLimitUnit);
const speedLimitPresetValues = useSettingsStore(state => state.speedLimitPresetValues); const speedLimitPresetValues = useSettingsStore(state => state.speedLimitPresetValues);
const setGlobalSpeedLimit = useSettingsStore(state => state.setGlobalSpeedLimit); const setGlobalSpeedLimit = useSettingsStore(state => state.setGlobalSpeedLimit);
const setLastCustomSpeedLimitKiB = useSettingsStore(state => state.setLastCustomSpeedLimitKiB); const setLastCustomSpeedLimitKiB = useSettingsStore(state => state.setLastCustomSpeedLimitKiB);
const setLastCustomSpeedLimitUnit = useSettingsStore(state => state.setLastCustomSpeedLimitUnit);
const setSpeedLimitPresetValues = useSettingsStore(state => state.setSpeedLimitPresetValues); const setSpeedLimitPresetValues = useSettingsStore(state => state.setSpeedLimitPresetValues);
const initial = parseLimit(globalSpeedLimit, lastCustomSpeedLimitKiB); const fallbackUnit: SpeedUnit = lastCustomSpeedLimitUnit === 'KB/s' ? 'KB/s' : 'MB/s';
const initial = parseLimit(globalSpeedLimit, lastCustomSpeedLimitKiB, fallbackUnit);
const [enabled, setEnabled] = useState(Boolean(globalSpeedLimit)); const [enabled, setEnabled] = useState(Boolean(globalSpeedLimit));
const [value, setValue] = useState(initial.value); const [value, setValue] = useState(String(initial.value));
const [unit, setUnit] = useState<SpeedUnit>(initial.unit); const [unit, setUnit] = useState<SpeedUnit>(initial.unit);
const [customPresetValue, setCustomPresetValue] = useState(initial.value); const [customPresetValue, setCustomPresetValue] = useState(String(initial.value));
const { addToast } = useToast(); const { addToast } = useToast();
const savingRef = useRef(false); const savingRef = useRef(false);
const presetValues = useMemo( const presetValues = useMemo(
@@ -77,12 +111,12 @@ export default function SpeedLimiterView() {
); );
useEffect(() => { useEffect(() => {
const parsed = parseLimit(globalSpeedLimit, lastCustomSpeedLimitKiB); const parsed = parseLimit(globalSpeedLimit, lastCustomSpeedLimitKiB, fallbackUnit);
setEnabled(Boolean(globalSpeedLimit)); setEnabled(Boolean(globalSpeedLimit));
setValue(parsed.value); setValue(String(parsed.value));
setUnit(parsed.unit); setUnit(parsed.unit);
setCustomPresetValue(parsed.value); setCustomPresetValue(String(parsed.value));
}, [globalSpeedLimit, lastCustomSpeedLimitKiB]); }, [globalSpeedLimit, lastCustomSpeedLimitKiB, fallbackUnit]);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
@@ -90,12 +124,13 @@ export default function SpeedLimiterView() {
const save = async () => { const save = async () => {
if (savingRef.current) return; if (savingRef.current) return;
savingRef.current = true; savingRef.current = true;
const numericValue = Math.max(1, Math.min(Number(value) || 1, unit === 'MB/s' ? 10240 : MAX_LIMIT_KIB)); const numericValue = clampSpeedDisplayValue(Number(value), unit);
const valueKiB = speedValueToKiB(numericValue, unit); const valueKiB = speedValueToKiB(numericValue, unit);
setIsSaving(true); setIsSaving(true);
try { try {
await setGlobalSpeedLimit(enabled ? `${valueKiB}K` : ''); await setGlobalSpeedLimit(enabled ? formatSpeedLimitForStorage(numericValue, unit) : '');
setLastCustomSpeedLimitKiB(valueKiB); setLastCustomSpeedLimitKiB(valueKiB);
setLastCustomSpeedLimitUnit(unit);
addToast({ addToast({
message: enabled ? `Global limit saved at ${numericValue} ${unit}` : 'Global speed limit disabled', message: enabled ? `Global limit saved at ${numericValue} ${unit}` : 'Global speed limit disabled',
variant: 'success' variant: 'success'
@@ -114,21 +149,22 @@ export default function SpeedLimiterView() {
const preset = (presetValue: number) => { const preset = (presetValue: number) => {
setEnabled(true); setEnabled(true);
setValue(displayValueFromPresetBase(presetValue, unit)); setValue(String(displayValueFromPresetBase(presetValue, unit)));
}; };
const applyCustomPreset = () => { const applyCustomPreset = () => {
const numericValue = Math.max(1, Math.min(Number(customPresetValue) || 1, unit === 'MB/s' ? MAX_LIMIT_MB : MAX_LIMIT_KIB)); const numericValue = clampSpeedDisplayValue(Number(customPresetValue), unit);
const presetBaseValue = Math.min(MAX_LIMIT_MB, presetBaseFromDisplayValue(numericValue, unit)); const presetBaseValue = Math.min(MAX_LIMIT_MB, presetBaseFromDisplayValue(numericValue, unit));
const nextPresets = sanitizePresetValues([...presetValues, presetBaseValue]); const nextPresets = sanitizePresetValues([...presetValues, presetBaseValue]);
const alreadyExists = nextPresets.length === presetValues.length; const alreadyExists = nextPresets.length === presetValues.length;
const storedPresetDisplayValue = displayValueFromPresetBase(presetBaseValue, unit);
setSpeedLimitPresetValues(nextPresets); setSpeedLimitPresetValues(nextPresets);
setEnabled(true); setEnabled(true);
setValue(numericValue); setValue(String(storedPresetDisplayValue));
addToast({ addToast({
message: alreadyExists message: alreadyExists
? `${formatPresetValue(numericValue)} ${unit} is already in quick presets` ? `${formatPresetValue(storedPresetDisplayValue)} ${unit} is already in quick presets`
: `Added ${formatPresetValue(numericValue)} ${unit} quick preset`, : `Added ${formatPresetValue(storedPresetDisplayValue)} ${unit} quick preset`,
variant: alreadyExists ? 'info' : 'success' variant: alreadyExists ? 'info' : 'success'
}); });
}; };
@@ -145,11 +181,15 @@ export default function SpeedLimiterView() {
const changeUnit = (nextUnit: SpeedUnit) => { const changeUnit = (nextUnit: SpeedUnit) => {
if (nextUnit === unit) return; if (nextUnit === unit) return;
setValue(convertSpeedValue(value, unit, nextUnit)); setValue(String(convertSpeedValue(Number(value), unit, nextUnit)));
setCustomPresetValue(convertSpeedValue(customPresetValue, unit, nextUnit)); setCustomPresetValue(String(convertSpeedValue(Number(customPresetValue), unit, nextUnit)));
setUnit(nextUnit); setUnit(nextUnit);
}; };
const currentDisplayValue = Number.isFinite(Number(value)) && Number(value) > 0
? value
: String(speedValueFromKiB(1, unit));
return ( return (
<div className="flex-1 flex h-full flex-col overflow-hidden bg-main-bg"> <div className="flex-1 flex h-full flex-col overflow-hidden bg-main-bg">
<WindowDragRegion /> <WindowDragRegion />
@@ -172,7 +212,7 @@ export default function SpeedLimiterView() {
<span className={`rounded-full px-2.5 py-1 text-[11px] font-semibold ${ <span className={`rounded-full px-2.5 py-1 text-[11px] font-semibold ${
enabled ? 'bg-accent/15 text-accent' : 'bg-item-hover text-text-muted' enabled ? 'bg-accent/15 text-accent' : 'bg-item-hover text-text-muted'
}`}> }`}>
{enabled ? `${value} ${unit}` : 'Unlimited'} {enabled ? `${currentDisplayValue} ${unit}` : 'Unlimited'}
</span> </span>
<button onClick={() => void save()} disabled={isSaving} className="app-button app-button-primary ml-auto px-3 text-[11px] disabled:opacity-50"> <button onClick={() => void save()} disabled={isSaving} className="app-button app-button-primary ml-auto px-3 text-[11px] disabled:opacity-50">
<Save size={14} /> Save Limit <Save size={14} /> Save Limit
@@ -191,11 +231,11 @@ export default function SpeedLimiterView() {
<div className="mt-6 flex items-center gap-3"> <div className="mt-6 flex items-center gap-3">
<input <input
type="number" type="number"
min="1" min={speedValueFromKiB(1, unit)}
step="any" step="any"
value={value} value={value}
disabled={!enabled || isSaving} disabled={!enabled || isSaving}
onChange={event => setValue(Math.max(1, Number(event.target.value) || 1))} onChange={event => setValue(event.target.value)}
className="app-control w-28 px-3 py-2 text-right font-mono" className="app-control w-28 px-3 py-2 text-right font-mono"
/> />
<div className="flex rounded-md border border-border-modal bg-bg-input p-1"> <div className="flex rounded-md border border-border-modal bg-bg-input p-1">
@@ -251,11 +291,11 @@ export default function SpeedLimiterView() {
<div className="ml-1 flex h-8 items-center gap-1.5 rounded-md border border-border-modal bg-bg-input px-2"> <div className="ml-1 flex h-8 items-center gap-1.5 rounded-md border border-border-modal bg-bg-input px-2">
<input <input
type="number" type="number"
min="1" min={speedValueFromKiB(1, unit)}
step="any" step="any"
value={customPresetValue} value={customPresetValue}
disabled={!enabled || isSaving} disabled={!enabled || isSaving}
onChange={event => setCustomPresetValue(Math.max(1, Number(event.target.value) || 1))} onChange={event => setCustomPresetValue(event.target.value)}
className="w-12 bg-transparent text-right font-mono text-[12px] text-text-primary outline-none disabled:opacity-50" className="w-12 bg-transparent text-right font-mono text-[12px] text-text-primary outline-none disabled:opacity-50"
aria-label={`Custom preset in ${unit}`} aria-label={`Custom preset in ${unit}`}
/> />
+9
View File
@@ -135,6 +135,7 @@ export interface SettingsState {
schedulerLastStartKey: string; schedulerLastStartKey: string;
schedulerLastStopKey: string; schedulerLastStopKey: string;
lastCustomSpeedLimitKiB: number; lastCustomSpeedLimitKiB: number;
lastCustomSpeedLimitUnit: string;
// Replicated SwiftUI App Settings // Replicated SwiftUI App Settings
perServerConnections: number; perServerConnections: number;
@@ -175,6 +176,7 @@ export interface SettingsState {
setSchedulerLastStartKey: (key: string) => void; setSchedulerLastStartKey: (key: string) => void;
setSchedulerLastStopKey: (key: string) => void; setSchedulerLastStopKey: (key: string) => void;
setLastCustomSpeedLimitKiB: (limit: number) => void; setLastCustomSpeedLimitKiB: (limit: number) => void;
setLastCustomSpeedLimitUnit: (unit: string) => void;
toggleSidebar: () => void; toggleSidebar: () => void;
setPerServerConnections: (count: number) => void; setPerServerConnections: (count: number) => void;
@@ -236,6 +238,7 @@ export const useSettingsStore = create<SettingsState>()(
schedulerLastStartKey: '', schedulerLastStartKey: '',
schedulerLastStopKey: '', schedulerLastStopKey: '',
lastCustomSpeedLimitKiB: 1024, lastCustomSpeedLimitKiB: 1024,
lastCustomSpeedLimitUnit: 'MB/s',
// Replicated SwiftUI defaults // Replicated SwiftUI defaults
perServerConnections: 16, perServerConnections: 16,
@@ -298,6 +301,7 @@ export const useSettingsStore = create<SettingsState>()(
setSchedulerLastStartKey: (schedulerLastStartKey) => set({ schedulerLastStartKey }), setSchedulerLastStartKey: (schedulerLastStartKey) => set({ schedulerLastStartKey }),
setSchedulerLastStopKey: (schedulerLastStopKey) => set({ schedulerLastStopKey }), setSchedulerLastStopKey: (schedulerLastStopKey) => set({ schedulerLastStopKey }),
setLastCustomSpeedLimitKiB: (lastCustomSpeedLimitKiB) => set({ lastCustomSpeedLimitKiB }), setLastCustomSpeedLimitKiB: (lastCustomSpeedLimitKiB) => set({ lastCustomSpeedLimitKiB }),
setLastCustomSpeedLimitUnit: (lastCustomSpeedLimitUnit) => set({ lastCustomSpeedLimitUnit }),
toggleSidebar: () => set((state) => ({ isSidebarVisible: !state.isSidebarVisible })), toggleSidebar: () => set((state) => ({ isSidebarVisible: !state.isSidebarVisible })),
setPerServerConnections: (perServerConnections) => set({ setPerServerConnections: (perServerConnections) => set({
@@ -447,6 +451,7 @@ export const useSettingsStore = create<SettingsState>()(
schedulerLastStartKey: state.schedulerLastStartKey, schedulerLastStartKey: state.schedulerLastStartKey,
schedulerLastStopKey: state.schedulerLastStopKey, schedulerLastStopKey: state.schedulerLastStopKey,
lastCustomSpeedLimitKiB: state.lastCustomSpeedLimitKiB, lastCustomSpeedLimitKiB: state.lastCustomSpeedLimitKiB,
lastCustomSpeedLimitUnit: state.lastCustomSpeedLimitUnit,
perServerConnections: state.perServerConnections, perServerConnections: state.perServerConnections,
maxAutomaticRetries: state.maxAutomaticRetries, maxAutomaticRetries: state.maxAutomaticRetries,
@@ -538,6 +543,10 @@ export const useSettingsStore = create<SettingsState>()(
speedLimitPresetValues: Array.isArray(persisted.speedLimitPresetValues) speedLimitPresetValues: Array.isArray(persisted.speedLimitPresetValues)
? persisted.speedLimitPresetValues ? persisted.speedLimitPresetValues
: currentState.speedLimitPresetValues, : currentState.speedLimitPresetValues,
lastCustomSpeedLimitUnit: persisted.lastCustomSpeedLimitUnit === 'KB/s'
|| persisted.lastCustomSpeedLimitUnit === 'MB/s'
? persisted.lastCustomSpeedLimitUnit
: currentState.lastCustomSpeedLimitUnit,
logsEnabled: persisted.logsEnabled === true, logsEnabled: persisted.logsEnabled === true,
approvedDownloadRoots: Array.isArray(persisted.approvedDownloadRoots) approvedDownloadRoots: Array.isArray(persisted.approvedDownloadRoots)
? persisted.approvedDownloadRoots ? persisted.approvedDownloadRoots