Add 6 new device providers, IconSelect grids, and UI fixes

New device providers: ESP-NOW, Philips Hue, USB HID, SPI Direct,
Razer Chroma SDK, and SteelSeries GameSense — each with client,
provider, full backend registration, schemas, routes, and frontend
support including discovery, form fields, and i18n.

Add IconSelect grids for SPI LED chipset selector and GameSense
peripheral type selector with new Lucide icons (cpu, keyboard,
mouse, headphones).

Replace emoji graph overlay buttons (eye, bell) with proper SVG
path icons for consistent cross-platform rendering.

Fix connection overlay causing horizontal scroll by adding
overflow: hidden.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 20:32:28 +03:00
parent 51ec0970c3
commit 272cb69247
28 changed files with 2752 additions and 10 deletions

View File

@@ -38,6 +38,20 @@ class Device:
dmx_protocol: str = "artnet",
dmx_start_universe: int = 0,
dmx_start_channel: int = 1,
# ESP-NOW fields
espnow_peer_mac: str = "",
espnow_channel: int = 1,
# Philips Hue fields
hue_username: str = "",
hue_client_key: str = "",
hue_entertainment_group_id: str = "",
# SPI Direct fields
spi_speed_hz: int = 800000,
spi_led_type: str = "WS2812B",
# Razer Chroma fields
chroma_device_type: str = "chromalink",
# SteelSeries GameSense fields
gamesense_device_type: str = "keyboard",
created_at: Optional[datetime] = None,
updated_at: Optional[datetime] = None,
):
@@ -57,6 +71,15 @@ class Device:
self.dmx_protocol = dmx_protocol
self.dmx_start_universe = dmx_start_universe
self.dmx_start_channel = dmx_start_channel
self.espnow_peer_mac = espnow_peer_mac
self.espnow_channel = espnow_channel
self.hue_username = hue_username
self.hue_client_key = hue_client_key
self.hue_entertainment_group_id = hue_entertainment_group_id
self.spi_speed_hz = spi_speed_hz
self.spi_led_type = spi_led_type
self.chroma_device_type = chroma_device_type
self.gamesense_device_type = gamesense_device_type
self.created_at = created_at or datetime.now(timezone.utc)
self.updated_at = updated_at or datetime.now(timezone.utc)
@@ -92,6 +115,24 @@ class Device:
d["dmx_start_universe"] = self.dmx_start_universe
if self.dmx_start_channel != 1:
d["dmx_start_channel"] = self.dmx_start_channel
if self.espnow_peer_mac:
d["espnow_peer_mac"] = self.espnow_peer_mac
if self.espnow_channel != 1:
d["espnow_channel"] = self.espnow_channel
if self.hue_username:
d["hue_username"] = self.hue_username
if self.hue_client_key:
d["hue_client_key"] = self.hue_client_key
if self.hue_entertainment_group_id:
d["hue_entertainment_group_id"] = self.hue_entertainment_group_id
if self.spi_speed_hz != 800000:
d["spi_speed_hz"] = self.spi_speed_hz
if self.spi_led_type != "WS2812B":
d["spi_led_type"] = self.spi_led_type
if self.chroma_device_type != "chromalink":
d["chroma_device_type"] = self.chroma_device_type
if self.gamesense_device_type != "keyboard":
d["gamesense_device_type"] = self.gamesense_device_type
return d
@classmethod
@@ -114,6 +155,15 @@ class Device:
dmx_protocol=data.get("dmx_protocol", "artnet"),
dmx_start_universe=data.get("dmx_start_universe", 0),
dmx_start_channel=data.get("dmx_start_channel", 1),
espnow_peer_mac=data.get("espnow_peer_mac", ""),
espnow_channel=data.get("espnow_channel", 1),
hue_username=data.get("hue_username", ""),
hue_client_key=data.get("hue_client_key", ""),
hue_entertainment_group_id=data.get("hue_entertainment_group_id", ""),
spi_speed_hz=data.get("spi_speed_hz", 800000),
spi_led_type=data.get("spi_led_type", "WS2812B"),
chroma_device_type=data.get("chroma_device_type", "chromalink"),
gamesense_device_type=data.get("gamesense_device_type", "keyboard"),
created_at=datetime.fromisoformat(data.get("created_at", datetime.now(timezone.utc).isoformat())),
updated_at=datetime.fromisoformat(data.get("updated_at", datetime.now(timezone.utc).isoformat())),
)
@@ -202,6 +252,15 @@ class DeviceStore:
dmx_protocol: str = "artnet",
dmx_start_universe: int = 0,
dmx_start_channel: int = 1,
espnow_peer_mac: str = "",
espnow_channel: int = 1,
hue_username: str = "",
hue_client_key: str = "",
hue_entertainment_group_id: str = "",
spi_speed_hz: int = 800000,
spi_led_type: str = "WS2812B",
chroma_device_type: str = "chromalink",
gamesense_device_type: str = "keyboard",
) -> Device:
"""Create a new device."""
device_id = f"device_{uuid.uuid4().hex[:8]}"
@@ -225,6 +284,15 @@ class DeviceStore:
dmx_protocol=dmx_protocol,
dmx_start_universe=dmx_start_universe,
dmx_start_channel=dmx_start_channel,
espnow_peer_mac=espnow_peer_mac,
espnow_channel=espnow_channel,
hue_username=hue_username,
hue_client_key=hue_client_key,
hue_entertainment_group_id=hue_entertainment_group_id,
spi_speed_hz=spi_speed_hz,
spi_led_type=spi_led_type,
chroma_device_type=chroma_device_type,
gamesense_device_type=gamesense_device_type,
)
self._devices[device_id] = device
@@ -257,6 +325,15 @@ class DeviceStore:
dmx_protocol: Optional[str] = None,
dmx_start_universe: Optional[int] = None,
dmx_start_channel: Optional[int] = None,
espnow_peer_mac: Optional[str] = None,
espnow_channel: Optional[int] = None,
hue_username: Optional[str] = None,
hue_client_key: Optional[str] = None,
hue_entertainment_group_id: Optional[str] = None,
spi_speed_hz: Optional[int] = None,
spi_led_type: Optional[str] = None,
chroma_device_type: Optional[str] = None,
gamesense_device_type: Optional[str] = None,
) -> Device:
"""Update device."""
device = self._devices.get(device_id)
@@ -289,6 +366,24 @@ class DeviceStore:
device.dmx_start_universe = dmx_start_universe
if dmx_start_channel is not None:
device.dmx_start_channel = dmx_start_channel
if espnow_peer_mac is not None:
device.espnow_peer_mac = espnow_peer_mac
if espnow_channel is not None:
device.espnow_channel = espnow_channel
if hue_username is not None:
device.hue_username = hue_username
if hue_client_key is not None:
device.hue_client_key = hue_client_key
if hue_entertainment_group_id is not None:
device.hue_entertainment_group_id = hue_entertainment_group_id
if spi_speed_hz is not None:
device.spi_speed_hz = spi_speed_hz
if spi_led_type is not None:
device.spi_led_type = spi_led_type
if chroma_device_type is not None:
device.chroma_device_type = chroma_device_type
if gamesense_device_type is not None:
device.gamesense_device_type = gamesense_device_type
device.updated_at = datetime.now(timezone.utc)
self.save()