diff --git a/browser/src/components/device-modal/serial-port.tsx b/browser/src/components/device-modal/serial-port.tsx index b03247b5..294076d5 100644 --- a/browser/src/components/device-modal/serial-port.tsx +++ b/browser/src/components/device-modal/serial-port.tsx @@ -1,10 +1,11 @@ -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { Button } from 'antd'; import { useAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; import { serialStateAtom } from '@/jotai/device.ts'; import { device } from '@/libs/device'; +import * as storage from '@/libs/storage'; type SerialPortProps = { onDisconnect: () => void; @@ -15,23 +16,64 @@ export const SerialPort = ({ setErrMsg, onDisconnect }: SerialPortProps) => { const { t } = useTranslation(); const [serialState, setSerialState] = useAtom(serialStateAtom); + const autoConnectAttempted = useRef(false); useEffect(() => { const isWebSerialSupported = 'serial' in navigator; if (!isWebSerialSupported) { setSerialState('notSupported'); + return; + } + + if (!autoConnectAttempted.current) { + autoConnectAttempted.current = true; + tryAutoConnect(); } }, [setSerialState]); + async function tryAutoConnect() { + const savedPort = storage.getSerialPort(); + if (!savedPort) return; + + try { + const grantedPorts = await navigator.serial.getPorts(); + const match = grantedPorts.find((port) => { + const info = port.getInfo(); + return ( + savedPort.vendorId != null && + savedPort.productId != null && + info.usbVendorId === savedPort.vendorId && + info.usbProductId === savedPort.productId + ); + }); + + if (match) { + setSerialState('connecting'); + await device.serialPort.init({ port: match, onDisconnect }); + setSerialState('connected'); + } + } catch (err) { + console.log('Auto-connect serial failed:', err); + } + } + const selectSerialPort = async () => { if (serialState === 'connecting') return; setSerialState('connecting'); setErrMsg(''); try { - const port = await navigator.serial.requestPort(); + const savedPort = storage.getSerialPort(); + const filters = savedPort?.vendorId != null + ? [{ usbVendorId: savedPort.vendorId, usbProductId: savedPort.productId }] + : []; + + const port = await navigator.serial.requestPort(filters.length ? { filters } : undefined); await device.serialPort.init({ port, onDisconnect }); + const info = port.getInfo(); + storage.setSerialPort(info.usbVendorId, info.usbProductId); + setSerialState('connected'); } catch (err) { console.log(err); diff --git a/browser/src/components/device-modal/video.tsx b/browser/src/components/device-modal/video.tsx index 90e7c218..b7132c67 100644 --- a/browser/src/components/device-modal/video.tsx +++ b/browser/src/components/device-modal/video.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Select } from 'antd'; import { useAtom, useAtomValue } from 'jotai'; import { useTranslation } from 'react-i18next'; @@ -20,6 +20,7 @@ export const Video = ({ setErrMsg }: VideoProps) => { const [videoState, setVideoState] = useAtom(videoStateAtom); const [devices, setDevices] = useState([]); + const autoSelectAttempted = useRef(false); useEffect(() => { getDevices(); @@ -51,13 +52,21 @@ export const Video = ({ setErrMsg }: VideoProps) => { }); setDevices(mediaDevices); + + if (!autoSelectAttempted.current) { + autoSelectAttempted.current = true; + const savedDeviceId = storage.getVideoDevice(); + if (savedDeviceId && mediaDevices.some((d) => d.videoId === savedDeviceId)) { + selectVideoDevice(savedDeviceId, mediaDevices); + } + } } catch (err) { console.log(err); setErrMsg(t('camera.failed')); } } - async function selectVideo(videoId: string) { + async function selectVideoDevice(videoId: string, deviceList: MediaDevice[]) { if (videoState === 'connecting') return; if (!videoId) { @@ -65,7 +74,7 @@ export const Video = ({ setErrMsg }: VideoProps) => { return; } - const device = devices.find((d) => d.videoId === videoId); + const device = deviceList.find((d) => d.videoId === videoId); if (!device) { return; } @@ -90,6 +99,10 @@ export const Video = ({ setErrMsg }: VideoProps) => { storage.setVideoDevice(videoId); } + async function selectVideo(videoId: string) { + return selectVideoDevice(videoId, devices); + } + return (