/** * Device discovery — add device modal, network/serial scanning, device type switching. */ import { _discoveryScanRunning, set_discoveryScanRunning, _discoveryCache, set_discoveryCache, csptCache, } from '../core/state.ts'; import { API_BASE, fetchWithAuth, isSerialDevice, isMockDevice, isMqttDevice, isWsDevice, isOpenrgbDevice, isDmxDevice, isDdpDevice, isOpcDevice, isEspnowDevice, isHueDevice, isYeelightDevice, isWizDevice, isLifxDevice, isGoveeDevice, isNanoleafDevice, isBleDevice, isUsbhidDevice, isSpiDevice, isChromaDevice, isGameSenseDevice, isGroupDevice, escapeHtml } from '../core/api.ts'; import { devicesCache } from '../core/state.ts'; import { t } from '../core/i18n.ts'; import { showToast, desktopFocus } from '../core/ui.ts'; import { Modal } from '../core/modal.ts'; import { _computeMaxFps, _renderFpsHint } from './devices.ts'; import { runPairingFlow, PairingCancelled } from './pairing-flow.ts'; import { getDeviceTypeIcon, ICON_RADIO, ICON_GLOBE, ICON_CPU, ICON_KEYBOARD, ICON_MOUSE, ICON_HEADPHONES, ICON_PLUG, ICON_TARGET_ICON, ICON_ACTIVITY, ICON_TEMPLATE, ICON_CHEVRON_UP, ICON_CHEVRON_DOWN, ICON_PLUS, ICON_TRASH, ICON_GIT_MERGE, ICON_COPY, ICON_BLUETOOTH, ICON_LIGHTBULB, ICON_SPARKLES, ICON_PALETTE } from '../core/icons.ts'; import { EntitySelect, EntityPalette } from '../core/entity-palette.ts'; import { IconSelect, showTypePicker } from '../core/icon-select.ts'; class AddDeviceModal extends Modal { constructor() { super('add-device-modal'); } snapshotValues() { return { name: (document.getElementById('device-name') as HTMLInputElement).value, type: (document.getElementById('device-type') as HTMLSelectElement).value, url: (document.getElementById('device-url') as HTMLInputElement).value, serialPort: (document.getElementById('device-serial-port') as HTMLSelectElement).value, ledCount: (document.getElementById('device-led-count') as HTMLInputElement).value, baudRate: (document.getElementById('device-baud-rate') as HTMLSelectElement).value, ledType: (document.getElementById('device-led-type') as HTMLSelectElement)?.value || 'rgb', sendLatency: (document.getElementById('device-send-latency') as HTMLInputElement)?.value || '0', zones: JSON.stringify(_getCheckedZones('device-zone-list')), zoneMode: _getZoneMode(), csptId: (document.getElementById('device-css-processing-template') as HTMLSelectElement)?.value || '', dmxProtocol: (document.getElementById('device-dmx-protocol') as HTMLSelectElement)?.value || 'artnet', dmxStartUniverse: (document.getElementById('device-dmx-start-universe') as HTMLInputElement)?.value || '0', dmxStartChannel: (document.getElementById('device-dmx-start-channel') as HTMLInputElement)?.value || '1', ddpPort: (document.getElementById('device-ddp-port') as HTMLInputElement)?.value || '0', ddpDestinationId: (document.getElementById('device-ddp-destination-id') as HTMLInputElement)?.value || '1', ddpColorOrder: (document.getElementById('device-ddp-color-order') as HTMLSelectElement)?.value || '1', opcChannel: (document.getElementById('device-opc-channel') as HTMLInputElement)?.value || '0', bleFamily: (document.getElementById('device-ble-family') as HTMLSelectElement)?.value || '', bleGoveeKey: (document.getElementById('device-ble-govee-key') as HTMLInputElement)?.value || '', yeelightMinInterval: (document.getElementById('device-yeelight-min-interval') as HTMLInputElement)?.value || '500', wizMinInterval: (document.getElementById('device-wiz-min-interval') as HTMLInputElement)?.value || '50', lifxMinInterval: (document.getElementById('device-lifx-min-interval') as HTMLInputElement)?.value || '50', goveeMinInterval: (document.getElementById('device-govee-min-interval') as HTMLInputElement)?.value || '50', nanoleafMinInterval: (document.getElementById('device-nanoleaf-min-interval') as HTMLInputElement)?.value || '100', groupChildren: JSON.stringify(_getGroupChildIds('device')), groupMode: (document.getElementById('device-group-mode-select') as HTMLSelectElement)?.value || 'sequence', }; } } const addDeviceModal = new AddDeviceModal(); /* ── Icon-grid type selector ──────────────────────────────────── */ const DEVICE_TYPE_KEYS = ['wled', 'adalight', 'ambiled', 'mqtt', 'ws', 'openrgb', 'dmx', 'ddp', 'opc', 'espnow', 'hue', 'yeelight', 'wiz', 'lifx', 'govee', 'nanoleaf', 'ble', 'usbhid', 'spi', 'chroma', 'gamesense', 'group', 'mock']; function _buildDeviceTypeItems() { return DEVICE_TYPE_KEYS.map(key => ({ value: key, icon: getDeviceTypeIcon(key), label: t(`device.type.${key}`), desc: t(`device.type.${key}.desc`), })); } let _deviceTypeIconSelect: any = null; let _csptEntitySelect: any = null; function _ensureDeviceTypeIconSelect() { const sel = document.getElementById('device-type'); if (!sel) return; if (_deviceTypeIconSelect) { _deviceTypeIconSelect.updateItems(_buildDeviceTypeItems()); return; } _deviceTypeIconSelect = new IconSelect({ target: sel, items: _buildDeviceTypeItems(), columns: 3 } as any); } function _ensureCsptEntitySelect() { const sel = document.getElementById('device-css-processing-template'); if (!sel) return; const templates = csptCache.data || []; // Populate native