import { useEffect, type ReactNode } from "react"; import { ArrowSyncRegular, SaveRegular } from "@fluentui/react-icons"; import { cx } from "../../lib/utils"; import { Button, Input, Modal, Select, Spinner, Tag } from "../ui"; import { isQmiControl } from "./shared"; import { DiscoveredDeviceRow } from "./DiscoveredDeviceRow"; import type { DiscoveredDevice } from "../../types"; import type { AddDeviceForm } from "./types"; import { useI18n } from "../../lib/i18n"; import { DEVICE_TYPES, deviceTypeImage } from "../../lib/deviceTypes"; export interface DeviceAddDialogProps { open: boolean; discovering: boolean; unconfiguredDiscovered: DiscoveredDevice[]; addSelected: DiscoveredDevice | null; addConfig: AddDeviceForm; addSaving: boolean; onClose: () => void; onRefresh: () => void; onSelectDevice: (d: DiscoveredDevice) => void; onConfigChange: (next: AddDeviceForm) => void; onSave: () => void; } function discoveryKey(d?: DiscoveredDevice | null): string { return d ? String(d.discoveryKey || `${d.usbPath || ""}|${d.atPort || ""}`) : ""; } function isQmiMode(d?: DiscoveredDevice | null): boolean { return String(d?.mode || "").toLowerCase() === "qmi"; } function modeLabel(d?: DiscoveredDevice | null): string { const m = String(d?.mode || "unknown").toLowerCase(); return m === "pcsc" ? "PC/SC" : m === "qmi" ? "QMI" : m === "mbim" ? "MBIM" : m === "ecm" ? "ECM" : m === "rndis" ? "RNDIS" : m === "ncm" ? "NCM" : "UNKNOWN"; } function Field({ label, children }: { label: ReactNode; children: ReactNode }) { return (
{children}
); } export function DeviceAddDialog(props: DeviceAddDialogProps) { const { t } = useI18n(); const { addSelected, addConfig } = props; const fixedQmi = isQmiControl(addSelected?.controlPath || addConfig?.controlDevice); const isMbim = String(addSelected?.mode || "").toLowerCase() === "mbim"; const isReader = addSelected?.hardwareKind === "pcsc" || String(addSelected?.mode || "").toLowerCase() === "pcsc"; useEffect(() => { if (fixedQmi && addConfig.deviceBackend !== "qmi") props.onConfigChange({ ...addConfig, deviceBackend: "qmi" }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fixedQmi]); useEffect(() => { if (isMbim && addConfig.deviceBackend !== "mbim") props.onConfigChange({ ...addConfig, deviceBackend: "mbim" }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isMbim]); const backendOptions = isReader ? [{ value: "pcsc", label: "PC/SC" }] : [ ...(isMbim ? [] : [ { value: "at", label: "AT", disabled: fixedQmi }, { value: "qmi", label: "QMI", disabled: !addConfig.controlDevice }, ]), ...(isMbim ? [{ value: "mbim", label: "MBIM" }] : []), ]; const set = (patch: Partial) => props.onConfigChange({ ...addConfig, ...patch }); return ( } >
{t("选择一个“未配置”的设备,系统将自动填充 AT 端口与识别信息。")}
{props.discovering ? (
{t("正在探测设备...")}
) : ( <> {props.unconfiguredDiscovered.map((d) => ( ))} {props.unconfiguredDiscovered.length === 0 ? (
{t("暂无可添加设备(或系统未发现新的模组)")}
) : null} )}
{addSelected ? (
{t("选定设备状态")}
{t("模式:")} {modeLabel(addSelected)} {fixedQmi ? {t("仅 QMI 后端")} : null} {isMbim ? {t("仅 MBIM 后端")} : null}
{fixedQmi ?
{t("此类 WWAN QMI 设备运行后端固定为 QMI;AT 口仍会保留给 AT 终端。")}
: null}
) : null}
{addConfig.deviceType ? : null}
set({ id: e.target.value })} placeholder={t("例如 ec20_3")} /> set({ name: e.target.value })} placeholder={t("显示名称(可选)")} /> {isReader ? set({ simPin: e.target.value })} maxLength={8} inputMode="numeric" placeholder={t("仅在 SIM 启用 PIN 时填写")} /> : null}
{t("设备后端模式")}
{fixedQmi ? t("固定 QMI,AT 口仅用于终端") : isMbim ? t("固定 MBIM,AT 口仅用于终端") : t("AT=串口 / QMI=纯 QMI")}