import { useCallback, useEffect, useState } from "react"; import { Button } from "@components/UI/Button.tsx"; import { useAppStore } from "@core/stores/appStore.ts"; import { useDeviceStore } from "@core/stores/deviceStore.ts"; import { subscribeAll } from "@core/subscriptions.ts"; import { randId } from "@core/utils/randId.ts"; import { MeshDevice } from "@meshtastic/core"; import { TransportWebSerial } from "@meshtastic/transport-web-serial"; import { AlertTriangle, Circle, Clock, Plus, Trash2, Usb } from "lucide-react"; import { useMessageStore } from "@core/stores/messageStore/index.ts"; import { useTranslation } from "react-i18next"; interface SerialTabProps { closeDialog: () => void; } export const SerialTab = ({ closeDialog }: SerialTabProps) => { const { t } = useTranslation("dialog"); const [connectionInProgress, setConnectionInProgress] = useState(false); const [connectingToPort, setConnectingToPort] = useState( null, ); const [serialPorts, setSerialPorts] = useState([]); const [connectionError, setConnectionError] = useState(null); const { addDevice } = useDeviceStore(); const messageStore = useMessageStore(); const { setSelectedDevice } = useAppStore(); const updateSerialPortList = useCallback(async () => { try { setSerialPorts((await navigator?.serial?.getPorts()) ?? []); } catch (error) { console.error("Error getting serial ports:", error); } }, []); useEffect(() => { const handleConnect = () => updateSerialPortList(); const handleDisconnect = () => updateSerialPortList(); navigator?.serial?.addEventListener("connect", handleConnect); navigator?.serial?.addEventListener("disconnect", handleDisconnect); updateSerialPortList(); return () => { navigator?.serial?.removeEventListener("connect", handleConnect); navigator?.serial?.removeEventListener("disconnect", handleDisconnect); }; }, [updateSerialPortList]); const connectToPort = async (port: SerialPort) => { setConnectingToPort(port); setConnectionError(null); try { const id = randId(); const device = addDevice(id); setSelectedDevice(id); const transport = await TransportWebSerial.createFromPort(port); const connection = new MeshDevice(transport, id); connection.configure(); device.addConnection(connection); subscribeAll(device, connection, messageStore); closeDialog(); } catch (error) { console.error("Serial connection error:", error); setConnectionError("Failed to connect to serial device"); } finally { setConnectingToPort(null); } }; const handleAddSerialDevice = async () => { setConnectionInProgress(true); setConnectionError(null); try { const port = await navigator.serial.requestPort(); setSerialPorts([...serialPorts, port]); } catch (error) { console.error("Error requesting port:", error); // Check for user cancellation - different browsers use different error types/messages const isDOMCancellation = error instanceof DOMException && error.name === "NotFoundError"; const isErrorCancellation = error instanceof Error && ( error.message.includes("cancelled") || error.message.includes("canceled") || error.message.includes("User cancelled") || error.message.includes("No port selected") ); const isUserCancellation = isDOMCancellation || isErrorCancellation; if (!isUserCancellation) { setConnectionError("Failed to add serial device"); } // If it's user cancellation, we silently ignore it (no error shown) } finally { setConnectionInProgress(false); } }; const removeSerialPort = (portToRemove: SerialPort) => { setSerialPorts(serialPorts.filter((port) => port !== portToRemove)); }; const getPortInfo = (port: SerialPort, index: number) => { const { usbProductId, usbVendorId } = port.getInfo(); const vendor = usbVendorId ? `0x${usbVendorId.toString(16).padStart(4, "0")}` : "Unknown"; const product = usbProductId ? `0x${usbProductId.toString(16).padStart(4, "0")}` : "Unknown"; return `Serial Port ${index + 1} (${vendor}:${product})`; }; const getStatusIcon = (port: SerialPort) => { const isConnecting = connectingToPort === port; const isConnected = port.readable !== null; if (isConnecting) { return ( ); } if (isConnected) { return ; } return ; }; const getStatusText = (port: SerialPort) => { if (connectingToPort === port) { return t("newDeviceDialog.tabs.status.connecting"); } return port.readable !== null ? t("newDeviceDialog.tabs.status.connected") : t("newDeviceDialog.tabs.status.available"); }; return (
{/* Header */}

{t("newDeviceDialog.tabs.serial.title")}

{/* Device List */}
{serialPorts.length === 0 ? (

{t("newDeviceDialog.tabs.serial.noDevices")}

{t("newDeviceDialog.tabs.serial.connectFirst")}

) : ( serialPorts.map((port, index) => (
{/* Status */}
{getStatusIcon(port)}
{/* Port Info */}
{getPortInfo(port, index)}
{getStatusText(port)}
{/* Actions */}
)) )} {/* Add Serial Device Button */} {/* Connection Error */} {connectionError && (

{connectionError}

)}
); };