Add baud rate selection and settings reset feature

Introduces baud rate selection for serial port connections in the UI and persists the value in local storage. Adds a settings reset tab to the menu, allowing users to clear all application settings and reload. Updates i18n files for new UI strings and refactors serial port logic to support dynamic baud rate changes.
This commit is contained in:
DarkAthena
2025-07-27 00:12:52 +08:00
parent 1e9b41530b
commit 116d9ecf6f
14 changed files with 406 additions and 69 deletions

View File

@@ -15,12 +15,17 @@ export class SerialPort {
): Promise<void> {
try {
if (this.port?.isOpen) {
console.log('Closing existing serial port before opening new one')
await this.close()
await new Promise(resolve => setTimeout(resolve, 100))
}
console.log(`Opening serial port: ${path} at ${baudRate} baud`)
this.port = new SP({ path, baudRate }, (err) => {
if (err) {
console.error('Error opening port: ', err.message)
} else {
console.log(`Serial port ${path} opened successfully at ${baudRate} baud`)
}
onOpen(err)
})
@@ -72,10 +77,24 @@ export class SerialPort {
async close(): Promise<void> {
if (this.port?.isOpen) {
try {
this.port.close()
console.log('Closing serial port...')
await new Promise<void>((resolve, reject) => {
this.port!.close((err) => {
if (err) {
console.error('close-serial-port error', err)
reject(err)
} else {
console.log('Serial port closed successfully')
resolve()
}
})
})
} catch (error) {
console.error('close-serial-port error', error)
throw error
}
} else {
console.log('Serial port is already closed or not initialized')
}
}
}

View File

@@ -26,7 +26,7 @@ async function getSerialPorts(): Promise<string[]> {
async function openSerialPort(
e: IpcMainInvokeEvent,
path: string,
baudRate = 57600
baudRate: number = 57600
): Promise<boolean> {
try {
await device.serialPort.init(path, baudRate, (err) => {

View File

@@ -1,10 +1,10 @@
import { ReactElement, useEffect, useState } from 'react'
import { Select } from 'antd'
import { Select, Space } from 'antd'
import { useAtom } from 'jotai'
import { useTranslation } from 'react-i18next'
import { IpcEvents } from '@common/ipc-events'
import { serialPortAtom, serialPortStateAtom } from '@renderer/jotai/device'
import { serialPortAtom, serialPortStateAtom, baudRateAtom } from '@renderer/jotai/device'
import * as storage from '@renderer/libs/storage'
type Option = {
@@ -21,11 +21,27 @@ export const SerialPort = ({ setMsg }: SerialPortProps): ReactElement => {
const [serialPort, setSerialPort] = useAtom(serialPortAtom)
const [serialPortState, setSerialPortState] = useAtom(serialPortStateAtom)
const [baudRate, setBaudRate] = useAtom(baudRateAtom)
const [options, setOptions] = useState<Option[]>([])
const [isFailed, setIsFailed] = useState(false)
const baudRateOptions = [
{ value: 1200, label: '1200' },
{ value: 2400, label: '2400' },
{ value: 4800, label: '4800' },
{ value: 9600, label: '9600' },
{ value: 14400, label: '14400' },
{ value: 19200, label: '19200' },
{ value: 38400, label: '38400' },
{ value: 57600, label: '57600' },
{ value: 115200, label: '115200' }
]
useEffect(() => {
const savedBaudRate = storage.getBaudRate()
setBaudRate(savedBaudRate)
getSerialPorts(true)
const rmListener = window.electron.ipcRenderer.on(IpcEvents.OPEN_SERIAL_PORT_RSP, (_, err) => {
@@ -63,7 +79,7 @@ export const SerialPort = ({ setMsg }: SerialPortProps): ReactElement => {
setIsFailed(false)
setMsg('')
const success = await window.electron.ipcRenderer.invoke(IpcEvents.OPEN_SERIAL_PORT, port)
const success = await window.electron.ipcRenderer.invoke(IpcEvents.OPEN_SERIAL_PORT, port, baudRate)
if (success) {
setSerialPort(port)
@@ -73,16 +89,45 @@ export const SerialPort = ({ setMsg }: SerialPortProps): ReactElement => {
}
}
async function closeSerialPort(): Promise<void> {
await window.electron.ipcRenderer.invoke(IpcEvents.CLOSE_SERIAL_PORT)
setSerialPort('')
setSerialPortState('disconnected')
storage.setSerialPort('')
}
async function handleBaudRateChange(newBaudRate: number): Promise<void> {
setBaudRate(newBaudRate)
storage.setBaudRate(newBaudRate)
if (serialPort && serialPortState === 'connected') {
const currentPort = serialPort
await closeSerialPort()
setTimeout(() => {
selectSerialPort(currentPort)
}, 200)
}
}
return (
<Select
value={serialPort || undefined}
style={{ width: 280 }}
options={options}
loading={serialPortState === 'connecting'}
status={isFailed ? 'error' : undefined}
placeholder={t('modal.selectSerial')}
onChange={selectSerialPort}
onClick={() => getSerialPorts(false)}
/>
<Space direction="vertical" size="small" style={{ width: '100%', alignItems: 'center' }}>
<Select
value={serialPort || undefined}
style={{ width: 280 }}
options={options}
loading={serialPortState === 'connecting'}
status={isFailed ? 'error' : undefined}
placeholder={t('modal.selectSerial')}
onChange={selectSerialPort}
onClick={() => getSerialPorts(false)}
/>
<Select
value={baudRate}
style={{ width: 280 }}
options={baudRateOptions}
placeholder={t('modal.selectBaudRate')}
onChange={handleBaudRateChange}
/>
</Space>
)
}

View File

@@ -1,19 +1,38 @@
import { ReactElement, useEffect, useState } from 'react'
import { Popover } from 'antd'
import { Popover, Select, Space, Divider } from 'antd'
import clsx from 'clsx'
import { useAtom } from 'jotai'
import { CpuIcon, LoaderCircleIcon, RadioIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { IpcEvents } from '@common/ipc-events'
import { serialPortAtom } from '@renderer/jotai/device'
import { serialPortAtom, baudRateAtom } from '@renderer/jotai/device'
import * as storage from '@renderer/libs/storage'
export const SerialPort = (): ReactElement => {
const { t } = useTranslation()
const [serialPort, setSerialPort] = useAtom(serialPortAtom)
const [baudRate, setBaudRate] = useAtom(baudRateAtom)
const [connectingPort, setConnectingPort] = useState('')
const [serialPorts, setSerialPorts] = useState<string[]>([])
const baudRateOptions = [
{ value: 1200, label: '1200' },
{ value: 2400, label: '2400' },
{ value: 4800, label: '4800' },
{ value: 9600, label: '9600' },
{ value: 14400, label: '14400' },
{ value: 19200, label: '19200' },
{ value: 38400, label: '38400' },
{ value: 57600, label: '57600' },
{ value: 115200, label: '115200' }
]
useEffect(() => {
const savedBaudRate = storage.getBaudRate()
setBaudRate(savedBaudRate)
getSerialPorts()
const rmListener = window.electron.ipcRenderer.on(IpcEvents.OPEN_SERIAL_PORT_RSP, () => {
@@ -34,37 +53,88 @@ export const SerialPort = (): ReactElement => {
if (connectingPort) return
setConnectingPort(port)
const success = await window.electron.ipcRenderer.invoke(IpcEvents.OPEN_SERIAL_PORT, port)
const success = await window.electron.ipcRenderer.invoke(IpcEvents.OPEN_SERIAL_PORT, port, baudRate)
if (success) {
setSerialPort(port)
storage.setSerialPort(port)
}
}
async function closeSerialPort(): Promise<void> {
await window.electron.ipcRenderer.invoke(IpcEvents.CLOSE_SERIAL_PORT)
setSerialPort('')
storage.setSerialPort('')
}
async function handleBaudRateChange(newBaudRate: number): Promise<void> {
setBaudRate(newBaudRate)
storage.setBaudRate(newBaudRate)
if (serialPort) {
const currentPort = serialPort
await closeSerialPort()
setTimeout(() => {
openSerialPort(currentPort)
}, 200)
}
}
const content = (
<div className="max-h-[350px] overflow-y-auto">
{serialPorts.map((port: string) => (
<div
key={port}
className={clsx(
'flex cursor-pointer items-center space-x-2 rounded px-3 py-2 hover:bg-neutral-700/60',
port === serialPort ? 'text-blue-500' : 'text-white'
)}
onClick={() => openSerialPort(port)}
>
{port === connectingPort ? (
<LoaderCircleIcon className="animate-spin" size={16} />
) : (
<RadioIcon size={16} />
)}
<span>{port}</span>
<div className="w-[280px] p-3">
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<div>
<div className="mb-2 text-sm text-neutral-300">{t('menu.serialPort.device')}</div>
<div className="max-h-[200px] overflow-y-auto">
{serialPorts.length === 0 ? (
<div className="text-neutral-500 text-sm py-2">{t('menu.serialPort.noDeviceFound')}</div>
) : (
serialPorts.map((port: string) => (
<div
key={port}
className={clsx(
'flex cursor-pointer items-center space-x-2 rounded px-3 py-2 hover:bg-neutral-700/60',
port === serialPort ? 'text-blue-500' : 'text-white'
)}
onClick={() => openSerialPort(port)}
>
{port === connectingPort ? (
<LoaderCircleIcon className="animate-spin" size={16} />
) : (
<RadioIcon size={16} />
)}
<span>{port}</span>
</div>
))
)}
</div>
</div>
))}
<Divider style={{ margin: 0, borderColor: '#404040' }} />
<div>
<div className="mb-2 text-sm text-neutral-300">{t('menu.serialPort.baudRate')}</div>
<Select
value={baudRate}
style={{ width: '100%' }}
options={baudRateOptions}
onChange={handleBaudRateChange}
size="small"
/>
</div>
</Space>
</div>
)
return (
<Popover content={content} placement="bottomLeft" trigger="click" arrow={false}>
<div className="flex h-[28px] cursor-pointer items-center justify-center rounded px-2 text-white hover:bg-neutral-700/70">
<div
className={clsx(
"flex h-[28px] cursor-pointer items-center justify-center rounded px-2 hover:bg-neutral-700/70",
serialPort ? 'text-blue-400' : 'text-white'
)}
title={serialPort ? `${serialPort} @ ${baudRate}` : t('menu.serialPort.clickToSelect')}
>
<CpuIcon size={18} />
</div>
</Popover>

View File

@@ -1,7 +1,7 @@
import { ReactElement, useEffect, useState } from 'react'
import { Badge, Modal } from 'antd'
import clsx from 'clsx'
import { BadgeInfoIcon, CircleArrowUpIcon, PaletteIcon, SettingsIcon } from 'lucide-react'
import { BadgeInfoIcon, CircleArrowUpIcon, PaletteIcon, SettingsIcon, RotateCcwIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { IpcEvents } from '@common/ipc-events'
@@ -10,6 +10,7 @@ import * as storage from '@renderer/libs/storage'
import { About } from './about'
import { Appearance } from './appearance'
import { Update } from './update'
import { Reset } from './reset'
export const Settings = (): ReactElement => {
const { t } = useTranslation()
@@ -32,6 +33,7 @@ export const Settings = (): ReactElement => {
const tabs = [
{ id: 'appearance', icon: <PaletteIcon size={16} />, component: <Appearance /> },
{ id: 'update', icon: <CircleArrowUpIcon size={16} />, component: <Update /> },
{ id: 'reset', icon: <RotateCcwIcon size={16} />, component: <Reset /> },
{ id: 'about', icon: <BadgeInfoIcon size={16} />, component: <About /> }
]

View File

@@ -0,0 +1,64 @@
import { ReactElement, useState } from 'react'
import { Button, Modal } from 'antd'
import { useTranslation } from 'react-i18next'
import * as storage from '@renderer/libs/storage'
export const Reset = (): ReactElement => {
const { t } = useTranslation()
const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false)
function handleReset(): void {
setIsConfirmModalOpen(true)
}
function confirmReset(): void {
storage.clearAllSettings()
setIsConfirmModalOpen(false)
// 重新加载应用以应用重置
window.location.reload()
}
function cancelReset(): void {
setIsConfirmModalOpen(false)
}
return (
<>
<div className="space-y-6">
<div>
<div className="text-xl font-bold">{t('settings.reset.title')}</div>
<div className="pt-2 text-sm text-neutral-400">{t('settings.reset.description')}</div>
</div>
<div className="space-y-4">
<div className="rounded-lg border border-red-600/30 bg-red-600/10 p-4">
<div className="text-sm font-medium text-red-400">{t('settings.reset.warning')}</div>
<div className="mt-2 text-xs text-red-300">{t('settings.reset.warningDescription')}</div>
</div>
<Button
type="primary"
danger
onClick={handleReset}
className="w-full"
>
{t('settings.reset.button')}
</Button>
</div>
</div>
<Modal
title={t('settings.reset.confirmTitle')}
open={isConfirmModalOpen}
onOk={confirmReset}
onCancel={cancelReset}
okText={t('settings.reset.confirm')}
cancelText={t('settings.reset.cancel')}
okButtonProps={{ danger: true }}
>
<p>{t('settings.reset.confirmMessage')}</p>
</Modal>
</>
)
}

View File

@@ -8,15 +8,22 @@ const be = {
failed: 'Kan geen verbinding maken met de camera. Probeer opnieuw.'
},
modal: {
title: 'Kies USB-apparaat',
selectVideo: 'Kies een video-invoerapparaat',
selectSerial: 'Kies serieel apparaat'
},
title: 'Kies USB-apparaat',
selectVideo: 'Selecteer een video-invoerapparaat',
selectSerial: 'Kies serieel apparaat',
selectBaudRate: 'Selecteer baudrate'
},
menu: {
serial: 'Serieel',
keyboard: 'Klavier',
mouse: 'Muis'
},
serial: 'Serieel',
keyboard: 'Toetsenbord',
mouse: 'Muis',
serialPort: {
device: 'Serieel apparaat',
baudRate: 'Baudrate',
noDeviceFound: 'Geen seriële apparaten gevonden',
clickToSelect: 'Klik om seriële poort te selecteren'
}
},
video: {
resolution: 'Resolutie',
customResolution: 'Aangepast',
@@ -72,6 +79,17 @@ const be = {
title: 'Over',
version: 'Versie',
community: 'Community'
},
reset: {
title: 'Instellingen resetten',
description: 'Alle applicatie-instellingen resetten naar standaardwaarden',
warning: 'Waarschuwing',
warningDescription: 'Deze actie kan niet ongedaan worden gemaakt. Alle aangepaste instellingen gaan verloren.',
button: 'Alle instellingen resetten',
confirmTitle: 'Reset bevestigen',
confirmMessage: 'Weet u zeker dat u alle instellingen wilt resetten? Deze actie kan niet ongedaan worden gemaakt.',
confirm: 'Reset',
cancel: 'Annuleren'
}
}
}

View File

@@ -8,15 +8,22 @@ const de = {
failed: 'Kamera konnte nicht verbunden werden. Bitte versuche es erneut.'
},
modal: {
title: 'USB-Gerät auswählen',
selectVideo: 'Bitte wähle ein Video-Eingabegerät aus',
selectSerial: 'Serielles Gerät auswählen'
},
title: 'USB-Gerät auswählen',
selectVideo: 'Bitte wählen Sie ein Video-Eingabegerät',
selectSerial: 'Serielles Gerät auswählen',
selectBaudRate: 'Bitte wählen Sie die Baudrate'
},
menu: {
serial: 'Seriell',
keyboard: 'Tastatur',
mouse: 'Maus'
},
serial: 'Seriell',
keyboard: 'Tastatur',
mouse: 'Maus',
serialPort: {
device: 'Serielles Gerät',
baudRate: 'Baudrate',
noDeviceFound: 'Keine seriellen Geräte gefunden',
clickToSelect: 'Klicken Sie, um seriellen Port auszuwählen'
}
},
video: {
resolution: 'Auflösung',
customResolution: 'Benutzerdefiniert',
@@ -72,6 +79,17 @@ const de = {
title: 'Über',
version: 'Version',
community: 'Community'
},
reset: {
title: 'Einstellungen zurücksetzen',
description: 'Alle Anwendungseinstellungen auf Standardwerte zurücksetzen',
warning: 'Warnung',
warningDescription: 'Diese Aktion kann nicht rückgängig gemacht werden. Alle benutzerdefinierten Einstellungen gehen verloren.',
button: 'Alle Einstellungen zurücksetzen',
confirmTitle: 'Zurücksetzen bestätigen',
confirmMessage: 'Sind Sie sicher, dass Sie alle Einstellungen zurücksetzen möchten? Diese Aktion kann nicht rückgängig gemacht werden.',
confirm: 'Zurücksetzen',
cancel: 'Abbrechen'
}
}
}

View File

@@ -10,12 +10,19 @@ const en = {
modal: {
title: 'Select USB Device',
selectVideo: 'Please select a video input device',
selectSerial: 'Please select serial device'
selectSerial: 'Please select serial device',
selectBaudRate: 'Please select baud rate'
},
menu: {
serial: 'Serial',
keyboard: 'Keyboard',
mouse: 'Mouse'
mouse: 'Mouse',
serialPort: {
device: 'Serial Device',
baudRate: 'Baud Rate',
noDeviceFound: 'No serial devices found',
clickToSelect: 'Click to select serial port'
}
},
video: {
resolution: 'Resolution',
@@ -75,6 +82,17 @@ const en = {
title: 'About',
version: 'Version',
community: 'Community'
},
reset: {
title: 'Reset Settings',
description: 'Reset all application settings to default values',
warning: 'Warning',
warningDescription: 'This action cannot be undone. All your custom settings will be lost.',
button: 'Reset All Settings',
confirmTitle: 'Confirm Reset',
confirmMessage: 'Are you sure you want to reset all settings? This action cannot be undone.',
confirm: 'Reset',
cancel: 'Cancel'
}
}
}

View File

@@ -8,15 +8,22 @@ const nl = {
failed: 'Kan geen verbinding maken met de camera. Probeer het opnieuw.'
},
modal: {
title: 'Selecteer USB-apparaat',
selectVideo: 'Selecteer een video-invoerapparaat',
selectSerial: 'Selecteer serieel apparaat'
},
title: 'Selecteer USB-apparaat',
selectVideo: 'Selecteer een video-invoerapparaat',
selectSerial: 'Selecteer serieel apparaat',
selectBaudRate: 'Selecteer baudrate'
},
menu: {
serial: 'Serieel',
keyboard: 'Toetsenbord',
mouse: 'Muis'
},
serial: 'Serieel',
keyboard: 'Toetsenbord',
mouse: 'Muis',
serialPort: {
device: 'Serieel apparaat',
baudRate: 'Baudrate',
noDeviceFound: 'Geen seriële apparaten gevonden',
clickToSelect: 'Klik om seriële poort te selecteren'
}
},
video: {
resolution: 'Resolutie',
customResolution: 'Aangepast',
@@ -72,6 +79,17 @@ const nl = {
title: 'Over',
version: 'Versie',
community: 'Community'
},
reset: {
title: 'Instellingen resetten',
description: 'Alle applicatie-instellingen resetten naar standaardwaarden',
warning: 'Waarschuwing',
warningDescription: 'Deze actie kan niet ongedaan worden gemaakt. Alle aangepaste instellingen gaan verloren.',
button: 'Alle instellingen resetten',
confirmTitle: 'Reset bevestigen',
confirmMessage: 'Weet je zeker dat je alle instellingen wilt resetten? Deze actie kan niet ongedaan worden gemaakt.',
confirm: 'Reset',
cancel: 'Annuleren'
}
}
}

View File

@@ -8,14 +8,21 @@ const ru = {
failed: 'Не удалось подключить камеру. Пожалуйста, попробуйте еще раз.'
},
modal: {
title: 'Выберите устройство USB',
selectVideo: 'Выбрать источник видео',
selectSerial: 'Выбрать последовательный порт'
title: 'Выбрать USB устройство',
selectVideo: 'Пожалуйста, выберите устройство видеовхода',
selectSerial: 'Выбрать последовательный порт',
selectBaudRate: 'Пожалуйста, выберите скорость передачи данных'
},
menu: {
serial: 'Последовательный порт',
keyboard: 'Клавиатура',
mouse: 'Мышь'
mouse: 'Мышь',
serialPort: {
device: 'Последовательное устройство',
baudRate: 'Скорость передачи',
noDeviceFound: 'Последовательные устройства не найдены',
clickToSelect: 'Нажмите, чтобы выбрать последовательный порт'
}
},
video: {
resolution: 'Разрешение',
@@ -72,6 +79,17 @@ const ru = {
title: 'О системе NanoKVM-USB',
version: 'Версия',
community: 'Сообщество'
},
reset: {
title: 'Сброс настроек',
description: 'Сбросить все настройки приложения к значениям по умолчанию',
warning: 'Предупреждение',
warningDescription: 'Это действие нельзя отменить. Все пользовательские настройки будут потеряны.',
button: 'Сбросить все настройки',
confirmTitle: 'Подтвердить сброс',
confirmMessage: 'Вы уверены, что хотите сбросить все настройки? Это действие нельзя отменить.',
confirm: 'Сбросить',
cancel: 'Отмена'
}
}
}

View File

@@ -9,12 +9,19 @@ const zh = {
modal: {
title: '选择 USB 设备',
selectVideo: '请选择视频输入设备',
selectSerial: '请选择串口设备'
selectSerial: '请选择串口设备',
selectBaudRate: '请选择波特率'
},
menu: {
serial: '串口',
keyboard: '键盘',
mouse: '鼠标'
mouse: '鼠标',
serialPort: {
device: '串口设备',
baudRate: '波特率',
noDeviceFound: '未找到串口设备',
clickToSelect: '点击选择串口'
}
},
video: {
resolution: '分辨率',
@@ -73,6 +80,17 @@ const zh = {
title: '关于',
version: '版本',
community: '社区'
},
reset: {
title: '重置设置',
description: '将所有应用程序设置重置为默认值',
warning: '警告',
warningDescription: '此操作无法撤销。所有自定义设置将丢失。',
button: '重置所有设置',
confirmTitle: '确认重置',
confirmMessage: '您确定要重置所有设置吗?此操作无法撤销。',
confirm: '重置',
cancel: '取消'
}
}
}

View File

@@ -15,3 +15,4 @@ export const videoStateAtom = atom<VideoState>('disconnected')
export const serialPortAtom = atom('')
export const serialPortStateAtom = atom<SerialState>('disconnected')
export const baudRateAtom = atom(57600)

View File

@@ -13,6 +13,7 @@ const MOUSE_MODE_KEY = 'nanokvm-usb-mouse-mode'
const MOUSE_SCROLL_DIRECTION_KEY = 'nanokvm-usb-mouse-scroll-direction'
const SKIP_UPDATE_KEY = 'nano-kvm-check-update'
const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-usb-mouse-scroll-interval'
const BAUD_RATE_KEY = 'nanokvm-usb-baud-rate'
export function getLanguage(): string | null {
return localStorage.getItem(LANGUAGE_KEY)
@@ -71,6 +72,18 @@ export function setSerialPort(port: string): void {
localStorage.setItem(SERIAL_PORT_KEY, port)
}
export function getBaudRate(): number {
const baudRate = localStorage.getItem(BAUD_RATE_KEY)
if (!baudRate) {
return 57600
}
return parseInt(baudRate, 10)
}
export function setBaudRate(baudRate: number): void {
localStorage.setItem(BAUD_RATE_KEY, baudRate.toString())
}
export function getIsMenuOpen(): boolean {
const state = localStorage.getItem(IS_MENU_OPEN_KEY)
if (!state) {
@@ -132,3 +145,18 @@ export function setSkipUpdate(skip: boolean): void {
const expiry = 3 * 24 * 60 * 60 * 1000
setWithExpiry(SKIP_UPDATE_KEY, String(skip), expiry)
}
export function clearAllSettings(): void {
localStorage.removeItem(LANGUAGE_KEY)
localStorage.removeItem(VIDEO_DEVICE_ID_KEY)
localStorage.removeItem(VIDEO_RESOLUTION_KEY)
localStorage.removeItem(CUSTOM_RESOLUTION_KEY)
localStorage.removeItem(SERIAL_PORT_KEY)
localStorage.removeItem(IS_MENU_OPEN_KEY)
localStorage.removeItem(MOUSE_STYLE_KEY)
localStorage.removeItem(MOUSE_MODE_KEY)
localStorage.removeItem(MOUSE_SCROLL_DIRECTION_KEY)
localStorage.removeItem(SKIP_UPDATE_KEY)
localStorage.removeItem(MOUSE_SCROLL_INTERVAL_KEY)
localStorage.removeItem(BAUD_RATE_KEY)
}