some webUI fixes
This commit is contained in:
@@ -298,11 +298,23 @@ class AppWindowLiveProcessingMixin:
|
||||
``_on_processing_live_settings_changed`` handler — one writer, one redraw —
|
||||
so the browser and the desktop never desync and ``processing_live.json`` has
|
||||
exactly one author. The per-widget change signals are neutralized by a
|
||||
suppression flag so the final handler runs once instead of dozens of times;
|
||||
a history command keeps its own server-managed sequence-bump path.
|
||||
suppression flag so the final handler runs once instead of dozens of times.
|
||||
|
||||
A destructive history command is not a settings edit: it routes to the very
|
||||
same handler the desktop's "Clear History" / "Remove Last" button uses, so the
|
||||
browser drains the ring readers, empties the runtime histories, clears the
|
||||
render caches and the processor's replay state, and redraws — rather than only
|
||||
nudging the processor while the on-screen history stays put.
|
||||
"""
|
||||
try:
|
||||
history_command = str(fields.get("history_command", "none"))
|
||||
if history_command == "clear_all":
|
||||
self._clear_all_runtime_history()
|
||||
return
|
||||
if history_command == "remove_last":
|
||||
self._remove_last_runtime_history()
|
||||
return
|
||||
|
||||
settings = {
|
||||
name: value
|
||||
for name, value in fields.items()
|
||||
@@ -310,10 +322,7 @@ class AppWindowLiveProcessingMixin:
|
||||
}
|
||||
# Log only field names (not values) so remote edits are traceable
|
||||
# without recording arbitrary client-supplied payloads.
|
||||
self._log_debug(
|
||||
f"Applying web live settings: fields={sorted(settings)}, "
|
||||
f"history_command={history_command}."
|
||||
)
|
||||
self._log_debug(f"Applying web live settings: fields={sorted(settings)}.")
|
||||
self._suppress_live_settings_handler = True
|
||||
try:
|
||||
# processor_mode first: dual-sourced gpr_* fields route to the gpr or
|
||||
@@ -324,9 +333,6 @@ class AppWindowLiveProcessingMixin:
|
||||
_set_web_live_field(self, name, value)
|
||||
finally:
|
||||
self._suppress_live_settings_handler = False
|
||||
|
||||
if history_command in {"clear_all", "remove_last"}:
|
||||
self._write_live_processing_config(history_command=history_command, bump_history_seq=True)
|
||||
self._on_processing_live_settings_changed()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._show_exception("Failed to apply web live settings", exc)
|
||||
|
||||
@@ -172,32 +172,39 @@ class AppWindowConfigProfileIOMixin:
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._show_exception("Failed to save config profile", exc)
|
||||
|
||||
def _load_config_from_dialog(self) -> None:
|
||||
"""Load full GUI profile from a user-selected JSON file."""
|
||||
def _ensure_ready_to_load_config(self) -> bool:
|
||||
"""Return whether a config load may proceed, reporting the blocker if not.
|
||||
|
||||
Loading rebuilds every widget and resizes history, so it is refused while a
|
||||
capture sequence or the pipeline is active — the operator must stop first.
|
||||
Shared by the desktop dialog and the web loader so both honor the same rule.
|
||||
"""
|
||||
if self._capture_session is not None:
|
||||
self._show_error(
|
||||
"Cannot load config during active capture sequence",
|
||||
details=self._capture_state_details(),
|
||||
)
|
||||
return
|
||||
return False
|
||||
if self._supervisor.is_running():
|
||||
self._show_error(
|
||||
"Stop all pipeline processes before loading a config profile",
|
||||
details=self._process_state_details(),
|
||||
)
|
||||
return
|
||||
return False
|
||||
return True
|
||||
|
||||
selected_path, _selected_filter = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Load Config Profile",
|
||||
str(self._active_profile_path),
|
||||
"JSON Files (*.json);;All Files (*)",
|
||||
)
|
||||
if not selected_path:
|
||||
return
|
||||
def _load_config_from_path(self, config_path: Path) -> None:
|
||||
"""Load a full GUI profile from ``config_path`` and apply it to the UI.
|
||||
|
||||
This is the single authoritative load path: the desktop "Load Config" dialog
|
||||
and the web config picker both funnel here, differing only in how they obtain
|
||||
the path. The pipeline is left untouched; live settings reach a running
|
||||
data_processor immediately while stable settings stage for the next Start.
|
||||
"""
|
||||
if not self._ensure_ready_to_load_config():
|
||||
return
|
||||
try:
|
||||
normalized_path = self._normalize_profile_path(Path(selected_path))
|
||||
normalized_path = self._normalize_profile_path(config_path)
|
||||
profile = GuiProfileModel.load_from_path(normalized_path)
|
||||
processor_running = self._supervisor.is_processor_running()
|
||||
self._apply_loaded_profile(profile, normalized_path)
|
||||
@@ -217,6 +224,20 @@ class AppWindowConfigProfileIOMixin:
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._show_exception("Failed to load config profile", exc)
|
||||
|
||||
def _load_config_from_dialog(self) -> None:
|
||||
"""Load a full GUI profile from a user-selected JSON file (desktop button)."""
|
||||
if not self._ensure_ready_to_load_config():
|
||||
return
|
||||
selected_path, _selected_filter = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Load Config Profile",
|
||||
str(self._active_profile_path),
|
||||
"JSON Files (*.json);;All Files (*)",
|
||||
)
|
||||
if not selected_path:
|
||||
return
|
||||
self._load_config_from_path(Path(selected_path))
|
||||
|
||||
def _apply_loaded_profile(self, profile: GuiProfileModel, profile_path: Path) -> None:
|
||||
"""Apply an already parsed profile to GUI state without restarting the pipeline.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user