mirror of
https://github.com/sipeed/NanoKVM-USB.git
synced 2026-09-12 06:29:59 -05:00
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:
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 /> }
|
||||
]
|
||||
|
||||
|
||||
64
desktop/src/renderer/src/components/menu/settings/reset.tsx
Normal file
64
desktop/src/renderer/src/components/menu/settings/reset.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: 'Отмена'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: '取消'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user