Unify graph docking, fix device hot-switch, and compact UI cards
- Unify minimap/toolbar/legend drag+dock into shared _makeDraggable() helper - Persist legend visibility and position, add active state to toggle buttons - Show custom colors only on graph cards (entity defaults remain in legend) - Replace emoji overlay buttons with SVG path icons - Fix stale is_running blocking target start (auto-clear if task is done) - Resolve device/target IDs to names in conflict error messages - Hot-switch LED device on running target via async stop-swap-start cycle - Compact automation dashboard cards and fix time_of_day localization - Inline CSS source pill on target cards to save vertical space Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -465,13 +465,33 @@ class ProcessorManager:
|
||||
proc = self._get_processor(target_id)
|
||||
proc.update_css_source(color_strip_source_id)
|
||||
|
||||
def update_target_device(self, target_id: str, device_id: str):
|
||||
"""Update the device for a target."""
|
||||
async def update_target_device(self, target_id: str, device_id: str):
|
||||
"""Update the device for a target.
|
||||
|
||||
If the target is currently running, performs a stop → swap → start
|
||||
cycle so the new device connection is established properly.
|
||||
"""
|
||||
proc = self._get_processor(target_id)
|
||||
if device_id not in self._devices:
|
||||
raise ValueError(f"Device {device_id} not registered")
|
||||
|
||||
was_running = proc.is_running
|
||||
if was_running:
|
||||
logger.info(
|
||||
"Hot-switching device for running target %s: stopping first",
|
||||
target_id,
|
||||
)
|
||||
await self.stop_processing(target_id)
|
||||
|
||||
proc.update_device(device_id)
|
||||
|
||||
if was_running:
|
||||
await self.start_processing(target_id)
|
||||
logger.info(
|
||||
"Hot-switch complete for target %s → device %s",
|
||||
target_id, device_id,
|
||||
)
|
||||
|
||||
def update_target_brightness_vs(self, target_id: str, vs_id: str):
|
||||
"""Update the brightness value source for a WLED target."""
|
||||
proc = self._get_processor(target_id)
|
||||
@@ -495,8 +515,25 @@ class ProcessorManager:
|
||||
and other.device_id == proc.device_id
|
||||
and other.is_running
|
||||
):
|
||||
# Stale state guard: if the task is actually finished,
|
||||
# clean up and allow starting instead of blocking.
|
||||
task = getattr(other, "_task", None)
|
||||
if task is not None and task.done():
|
||||
logger.warning(
|
||||
"Processor %s had stale is_running=True (task done) — clearing",
|
||||
other_id,
|
||||
)
|
||||
other._is_running = False
|
||||
continue
|
||||
|
||||
dev_name = proc.device_id
|
||||
tgt_name = other_id
|
||||
if self._device_store:
|
||||
dev = self._device_store.get_device(proc.device_id)
|
||||
if dev:
|
||||
dev_name = dev.name
|
||||
raise RuntimeError(
|
||||
f"Device {proc.device_id} is already being processed by target {other_id}"
|
||||
f"Device '{dev_name}' is already being processed by target {tgt_name}"
|
||||
)
|
||||
|
||||
# Close cached idle client — processor creates its own connection
|
||||
|
||||
Reference in New Issue
Block a user