mirror of
https://github.com/sipeed/NanoKVM-USB.git
synced 2026-09-11 05:59:57 -05:00
Merge pull request #60 from Dark-Athena/main
Add baud rate selection and settings reset feature
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',
|
||||
scale: 'Schaal',
|
||||
@@ -73,6 +80,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',
|
||||
scale: 'Skalierung',
|
||||
@@ -76,6 +83,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',
|
||||
@@ -81,6 +88,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',
|
||||
scale: 'Schaal',
|
||||
@@ -73,6 +80,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: 'Разрешение',
|
||||
@@ -77,6 +84,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: '分辨率',
|
||||
@@ -79,6 +86,17 @@ const zh = {
|
||||
title: '关于',
|
||||
version: '版本',
|
||||
community: '社区'
|
||||
},
|
||||
reset: {
|
||||
title: '重置设置',
|
||||
description: '将所有应用程序设置重置为默认值',
|
||||
warning: '警告',
|
||||
warningDescription: '此操作无法撤销。所有自定义设置将丢失。',
|
||||
button: '重置所有设置',
|
||||
confirmTitle: '确认重置',
|
||||
confirmMessage: '您确定要重置所有设置吗?此操作无法撤销。',
|
||||
confirm: '重置',
|
||||
cancel: '取消'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,3 +17,4 @@ export const videoStateAtom = atom<VideoState>('disconnected')
|
||||
|
||||
export const serialPortAtom = atom('')
|
||||
export const serialPortStateAtom = atom<SerialState>('disconnected')
|
||||
export const baudRateAtom = atom(57600)
|
||||
|
||||
@@ -14,6 +14,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'
|
||||
const MOUSE_JIGGLER_MODE_KEY = 'nanokvm-usb-mouse-jiggler-mode'
|
||||
|
||||
export function getLanguage(): string | null {
|
||||
@@ -85,6 +86,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) {
|
||||
@@ -147,6 +160,19 @@ export function setSkipUpdate(skip: boolean): void {
|
||||
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)
|
||||
export function getMouseJigglerMode(): 'enable' | 'disable' {
|
||||
const jiggler = localStorage.getItem(MOUSE_JIGGLER_MODE_KEY)
|
||||
return jiggler && jiggler === 'enable' ? 'enable' : 'disable'
|
||||
|
||||
Reference in New Issue
Block a user