From db5aa8d73d1b991db11835c6d5ba063f0476b998 Mon Sep 17 00:00:00 2001 From: ayzen Date: Thu, 16 Oct 2025 20:37:26 +0300 Subject: [PATCH] some russian translations and S button functios as single sweep now --- vna_system/api/endpoints/acquisition.py | 24 ++--- vna_system/api/endpoints/health.py | 2 +- vna_system/api/endpoints/settings.py | 100 +++++++++--------- vna_system/api/endpoints/web_ui.py | 10 +- .../processors/configs/magnitude_config.json | 2 +- vna_system/web_ui/static/css/components.css | 17 +++ vna_system/web_ui/static/css/layout.css | 4 + vna_system/web_ui/static/js/main.js | 19 +++- vna_system/web_ui/templates/index.html | 3 +- 9 files changed, 110 insertions(+), 71 deletions(-) diff --git a/vna_system/api/endpoints/acquisition.py b/vna_system/api/endpoints/acquisition.py index 85d1ca4..2fcfacf 100644 --- a/vna_system/api/endpoints/acquisition.py +++ b/vna_system/api/endpoints/acquisition.py @@ -26,7 +26,7 @@ async def get_acquisition_status() -> dict[str, Any]: acquisition = singletons.vna_data_acquisition_instance if acquisition is None: logger.error("Acquisition singleton is not initialized") - raise HTTPException(status_code=500, detail="Acquisition not initialized") + raise HTTPException(status_code=500, detail="Сбор данных не инициализирован") return { "running": acquisition.is_running, @@ -45,14 +45,14 @@ async def start_acquisition() -> dict[str, Any]: acquisition = singletons.vna_data_acquisition_instance if acquisition is None: logger.error("Acquisition singleton is not initialized") - raise HTTPException(status_code=500, detail="Acquisition not initialized") + raise HTTPException(status_code=500, detail="Сбор данных не инициализирован") if not acquisition.is_running: acquisition.start() logger.info("Acquisition thread started via API") acquisition.set_continuous_mode(True) - return {"success": True, "message": "Acquisition started"} + return {"success": True, "message": "Сбор данных запущен"} except HTTPException: raise except Exception as exc: # noqa: BLE001 @@ -69,14 +69,14 @@ async def stop_acquisition() -> dict[str, Any]: acquisition = singletons.vna_data_acquisition_instance if acquisition is None: logger.error("Acquisition singleton is not initialized") - raise HTTPException(status_code=500, detail="Acquisition not initialized") + raise HTTPException(status_code=500, detail="Сбор данных не инициализирован") if not acquisition.is_running: - return {"success": True, "message": "Acquisition already stopped"} + return {"success": True, "message": "Сбор данных уже остановлен"} acquisition.pause() logger.info("Acquisition paused via API") - return {"success": True, "message": "Acquisition paused"} + return {"success": True, "message": "Сбор данных приостановлен"} except HTTPException: raise except Exception as exc: # noqa: BLE001 @@ -94,14 +94,14 @@ async def trigger_single_sweep() -> dict[str, Any]: acquisition = singletons.vna_data_acquisition_instance if acquisition is None: logger.error("Acquisition singleton is not initialized") - raise HTTPException(status_code=500, detail="Acquisition not initialized") + raise HTTPException(status_code=500, detail="Сбор данных не инициализирован") if not acquisition.is_running: acquisition.start() logger.info("Acquisition thread started (single-sweep request)") acquisition.trigger_single_sweep() - return {"success": True, "message": "Single sweep triggered"} + return {"success": True, "message": "Одиночный свип запущен"} except HTTPException: raise except Exception as exc: # noqa: BLE001 @@ -111,7 +111,7 @@ async def trigger_single_sweep() -> dict[str, Any]: @router.get("/acquisition/latest-sweep") async def get_latest_sweep( - limit: int = Query(10, ge=1, le=1000, description="Max number of points to include in response"), + limit: int = Query(10, ge=1, le=1000, description="Максимальное число точек в ответе"), ) -> dict[str, Any]: """ Return the latest sweep metadata and a limited subset of points. @@ -125,11 +125,11 @@ async def get_latest_sweep( acquisition = singletons.vna_data_acquisition_instance if acquisition is None: logger.error("Acquisition singleton is not initialized") - raise HTTPException(status_code=500, detail="Acquisition not initialized") + raise HTTPException(status_code=500, detail="Сбор данных не инициализирован") latest_sweep = acquisition.sweep_buffer.get_latest_sweep() if not latest_sweep: - return {"sweep": None, "message": "No sweep data available"} + return {"sweep": None, "message": "Данные свипа недоступны"} points = latest_sweep.points[:limit] return { @@ -139,7 +139,7 @@ async def get_latest_sweep( "total_points": latest_sweep.total_points, "points": points, }, - "message": f"Latest sweep #{latest_sweep.sweep_number} with {latest_sweep.total_points} points", + "message": f"Последний свип №{latest_sweep.sweep_number}, точек: {latest_sweep.total_points}", } except HTTPException: raise diff --git a/vna_system/api/endpoints/health.py b/vna_system/api/endpoints/health.py index 49a94d9..8d27956 100644 --- a/vna_system/api/endpoints/health.py +++ b/vna_system/api/endpoints/health.py @@ -14,4 +14,4 @@ router = APIRouter(prefix="/api/v1", tags=["health"]) @router.get("/") async def root(): """Root endpoint.""" - return {"message": "VNA System API", "version": "1.0.0"} + return {"message": "API системы VNA", "version": "1.0.0"} diff --git a/vna_system/api/endpoints/settings.py b/vna_system/api/endpoints/settings.py index e019eeb..d9dab48 100644 --- a/vna_system/api/endpoints/settings.py +++ b/vna_system/api/endpoints/settings.py @@ -67,14 +67,14 @@ async def set_preset(request: SetPresetRequest) -> dict[str, Any]: presets = singletons.settings_manager.get_available_presets() preset = next((p for p in presets if p.filename == request.filename), None) if preset is None: - raise HTTPException(status_code=404, detail=f"Preset not found: {request.filename}") + raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.filename}") # Changing preset invalidates active calibration selection. singletons.settings_manager.calibration_manager.clear_current_calibration() singletons.settings_manager.set_current_preset(preset) logger.info("Preset selected via API", filename=preset.filename, mode=preset.mode.value) - return {"success": True, "message": f"Preset set to {request.filename}"} + return {"success": True, "message": f"Пресет установлен: {request.filename}"} except HTTPException: raise except Exception as exc: # noqa: BLE001 @@ -112,7 +112,7 @@ async def get_calibrations(preset_filename: str | None = None) -> list[Calibrati presets = singletons.settings_manager.get_available_presets() preset = next((p for p in presets if p.filename == preset_filename), None) if preset is None: - raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}") + raise HTTPException(status_code=404, detail=f"Пресет не найден: {preset_filename}") calibrations = singletons.settings_manager.get_available_calibrations(preset) details: list[CalibrationModel] = [] @@ -153,14 +153,14 @@ async def start_calibration(request: StartCalibrationRequest) -> dict[str, Any]: presets = singletons.settings_manager.get_available_presets() preset = next((p for p in presets if p.filename == request.preset_filename), None) if preset is None: - raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}") + raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.preset_filename}") calib = singletons.settings_manager.start_new_calibration(preset) required = singletons.settings_manager.get_required_standards(calib.preset.mode) return { "success": True, - "message": "Calibration started", + "message": "Калибровка запущена", "preset": calib.preset.filename, "required_standards": [s.value for s in required], } @@ -178,7 +178,7 @@ async def add_calibration_standard(request: CalibrateStandardRequest) -> dict[st try: standard = CalibrationStandard(request.standard) except ValueError: - raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}") + raise HTTPException(status_code=400, detail=f"Недопустимый стандарт калибровки: {request.standard}") sweep_no = singletons.settings_manager.capture_calibration_standard_from_acquisition( standard, singletons.vna_data_acquisition_instance @@ -189,7 +189,7 @@ async def add_calibration_standard(request: CalibrateStandardRequest) -> dict[st return { "success": True, - "message": f"Added {standard.value} standard from sweep {sweep_no}", + "message": f"Добавлен стандарт {standard.value} из свипа {sweep_no}", "sweep_number": sweep_no, "progress": f"{progress[0]}/{progress[1]}", "is_complete": working.is_complete() if working else False, @@ -208,7 +208,7 @@ async def save_calibration(request: SaveCalibrationRequest) -> dict[str, Any]: saved = singletons.settings_manager.save_calibration_set(request.name) return { "success": True, - "message": f"Calibration '{request.name}' saved successfully", + "message": f"Калибровка \"{request.name}\" успешно сохранена", "preset": saved.preset.filename, "standards": [s.value for s in saved.standards.keys()], } @@ -226,10 +226,10 @@ async def set_calibration(request: SetCalibrationRequest) -> dict[str, Any]: presets = singletons.settings_manager.get_available_presets() preset = next((p for p in presets if p.filename == request.preset_filename), None) if preset is None: - raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}") + raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.preset_filename}") singletons.settings_manager.set_current_calibration(request.name, preset) - return {"success": True, "message": f"Calibration set to '{request.name}'"} + return {"success": True, "message": f"Калибровка установлена: \"{request.name}\""} except HTTPException: raise except Exception as exc: # noqa: BLE001 @@ -266,7 +266,7 @@ async def remove_calibration_standard(request: RemoveStandardRequest) -> dict[st try: standard = CalibrationStandard(request.standard) except ValueError: - raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}") + raise HTTPException(status_code=400, detail=f"Недопустимый стандарт калибровки: {request.standard}") singletons.settings_manager.remove_calibration_standard(standard) @@ -275,7 +275,7 @@ async def remove_calibration_standard(request: RemoveStandardRequest) -> dict[st return { "success": True, - "message": f"Removed {standard.value} standard", + "message": f"Стандарт {standard.value} удалён", "progress": f"{progress[0]}/{progress[1]}", "is_complete": working.is_complete() if working else False, } @@ -319,18 +319,18 @@ async def get_calibration_standards_plots( presets = singletons.settings_manager.get_available_presets() preset = next((p for p in presets if p.filename == preset_filename), None) if preset is None: - raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}") + raise HTTPException(status_code=404, detail=f"Пресет не найден: {preset_filename}") else: preset = singletons.settings_manager.get_current_preset() if preset is None: - raise HTTPException(status_code=400, detail="No current preset selected") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран") # Resolve calibration directory (uses manager's internal layout) calibration_manager = singletons.settings_manager.calibration_manager calibration_dir = calibration_manager._get_preset_calibration_dir(preset) / calibration_name # noqa: SLF001 if not calibration_dir.exists(): - raise HTTPException(status_code=404, detail=f"Calibration not found: {calibration_name}") + raise HTTPException(status_code=404, detail=f"Калибровка не найдена: {calibration_name}") individual_plots = generate_standards_magnitude_plots(calibration_dir, preset) @@ -352,9 +352,9 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]: try: working = singletons.settings_manager.get_current_working_calibration() if not working: - raise HTTPException(status_code=404, detail="No working calibration active") + raise HTTPException(status_code=404, detail="Рабочая калибровка не активна") if not working.standards: - raise HTTPException(status_code=404, detail="No standards captured in working calibration") + raise HTTPException(status_code=404, detail="В рабочей калибровке нет сохранённых стандартов") from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data @@ -371,8 +371,8 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]: fig = generate_magnitude_plot_from_sweep_data(sweep, working.preset) if "error" not in fig and fig.get("data"): fig["data"][0]["line"]["color"] = standard_colors.get(standard.value, "#1f77b4") - fig["data"][0]["name"] = f"{standard.value.upper()} Standard" - fig["layout"]["title"] = f"{standard.value.upper()} Standard Magnitude (Working)" + fig["data"][0]["name"] = f"Стандарт {standard.value.upper()}" + fig["layout"]["title"] = f"Амплитуда стандарта {standard.value.upper()} (рабочая)" fig["raw_sweep_data"] = { "sweep_number": sweep.sweep_number, @@ -390,13 +390,13 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]: } individual[standard.value] = fig except Exception as exc: # noqa: BLE001 - individual[standard.value] = {"error": f"Failed to generate plot for {standard.value}: {exc}"} + individual[standard.value] = {"error": f"Не удалось построить график для {standard.value}: {exc}"} if not individual: - raise HTTPException(status_code=404, detail="No valid plots generated for working calibration") + raise HTTPException(status_code=404, detail="Не удалось создать графики для рабочей калибровки") return { - "calibration_name": "Working Calibration", + "calibration_name": "Рабочая калибровка", "preset": {"filename": working.preset.filename, "mode": working.preset.mode.value}, "individual_plots": individual, "is_working": True, @@ -425,11 +425,11 @@ async def get_references(preset_filename: str | None = None) -> List[dict[str, A None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") references = settings_manager.get_available_references(preset) @@ -462,11 +462,11 @@ async def get_current_reference(preset_filename: str | None = None) -> dict[str, None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") current_ref = settings_manager.get_current_reference(preset) @@ -500,11 +500,11 @@ async def create_reference(request: CreateReferenceRequest) -> dict[str, Any]: None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{request.preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{request.preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") # Create reference using the new capture method reference_info = settings_manager.capture_reference_from_acquisition( @@ -517,7 +517,7 @@ async def create_reference(request: CreateReferenceRequest) -> dict[str, Any]: return { "success": True, - "message": f"Reference '{request.name}' created successfully", + "message": f"Эталон \"{request.name}\" успешно создан", "reference": { "name": reference_info.name, "timestamp": reference_info.timestamp.isoformat(), @@ -545,20 +545,20 @@ async def set_current_reference(request: SetReferenceRequest) -> dict[str, Any]: None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{request.preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{request.preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") success = settings_manager.set_current_reference(request.name, preset) if not success: - raise HTTPException(status_code=404, detail=f"Reference '{request.name}' not found") + raise HTTPException(status_code=404, detail=f"Эталон \"{request.name}\" не найден") return { "success": True, - "message": f"Reference '{request.name}' set as current" + "message": f"Эталон \"{request.name}\" выбран текущим" } except HTTPException: raise @@ -575,11 +575,11 @@ async def clear_current_reference() -> dict[str, Any]: success = settings_manager.clear_current_reference() if not success: - raise HTTPException(status_code=500, detail="Failed to clear current reference") + raise HTTPException(status_code=500, detail="Не удалось сбросить текущий эталон") return { "success": True, - "message": "Current reference cleared" + "message": "Текущий эталон сброшен" } except HTTPException: raise @@ -600,15 +600,15 @@ async def get_reference(reference_name: str, preset_filename: str | None = None) None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") info = settings_manager.get_reference_info(reference_name, preset) if not info: - raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found") + raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден") return ReferenceModel( name=info.name, @@ -636,19 +636,19 @@ async def get_reference_plot(reference_name: str, preset_filename: str | None = None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") reference_info = settings_manager.get_reference_info(reference_name, preset) if not reference_info: - raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found") + raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден") sweep_data = settings_manager.reference_manager.get_reference_sweep(reference_name, preset) if not sweep_data: - raise HTTPException(status_code=404, detail=f"Reference data not found for '{reference_name}'") + raise HTTPException(status_code=404, detail=f"Данные эталона \"{reference_name}\" не найдены") from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data @@ -682,20 +682,20 @@ async def delete_reference(reference_name: str, preset_filename: str | None = No None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") success = settings_manager.delete_reference(reference_name, preset) if not success: - raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found") + raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден") return { "success": True, - "message": f"Reference '{reference_name}' deleted successfully" + "message": f"Эталон \"{reference_name}\" успешно удалён" } except HTTPException: raise @@ -709,7 +709,7 @@ async def clear_current_calibration() -> dict[str, Any]: """Clear the current calibration selection.""" try: singletons.settings_manager.calibration_manager.clear_current_calibration() - return {"success": True, "message": "Current calibration cleared"} + return {"success": True, "message": "Текущая калибровка сброшена"} except Exception as exc: # noqa: BLE001 logger.error("Failed to clear current calibration", error=repr(exc)) raise HTTPException(status_code=500, detail=str(exc)) @@ -727,17 +727,17 @@ async def delete_calibration(calibration_name: str, preset_filename: str | None None ) if not preset: - raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found") + raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден") else: preset = settings_manager.get_current_preset() if not preset: - raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided") + raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан") settings_manager.delete_calibration(preset, calibration_name) return { "success": True, - "message": f"Calibration '{calibration_name}' deleted" + "message": f"Калибровка \"{calibration_name}\" удалена" } except HTTPException: raise diff --git a/vna_system/api/endpoints/web_ui.py b/vna_system/api/endpoints/web_ui.py index de7d931..cd8be88 100644 --- a/vna_system/api/endpoints/web_ui.py +++ b/vna_system/api/endpoints/web_ui.py @@ -28,7 +28,7 @@ async def dashboard(): if not index_file.exists(): logger.error(f"Dashboard template not found: {index_file}") return HTMLResponse( - content="

Dashboard Not Available

The web UI template could not be found.

", + content="

Панель недоступна

Не удалось найти шаблон веб-интерфейса.

", status_code=404 ) @@ -40,7 +40,7 @@ async def dashboard(): except Exception as e: logger.error(f"Error serving dashboard: {e}") return HTMLResponse( - content=f"

Error

Unable to load dashboard: {e}

", + content=f"

Ошибка

Не удалось загрузить панель: {e}

", status_code=500 ) @@ -48,10 +48,10 @@ async def dashboard(): async def health_ui(): """Health check endpoint for web UI.""" return { - "service": "VNA Web UI", - "status": "healthy", + "service": "Веб-интерфейс VNA", + "status": "работает", "web_ui_dir": str(WEB_UI_DIR), "static_dir_exists": STATIC_DIR.exists(), "templates_dir_exists": TEMPLATES_DIR.exists(), "index_exists": (TEMPLATES_DIR / "index.html").exists() - } \ No newline at end of file + } diff --git a/vna_system/core/processors/configs/magnitude_config.json b/vna_system/core/processors/configs/magnitude_config.json index b01ac9b..edce130 100644 --- a/vna_system/core/processors/configs/magnitude_config.json +++ b/vna_system/core/processors/configs/magnitude_config.json @@ -4,5 +4,5 @@ "autoscale": true, "show_magnitude": true, "show_phase": false, - "open_air": false + "open_air": true } \ No newline at end of file diff --git a/vna_system/web_ui/static/css/components.css b/vna_system/web_ui/static/css/components.css index 8c4a361..ef2b976 100644 --- a/vna_system/web_ui/static/css/components.css +++ b/vna_system/web_ui/static/css/components.css @@ -94,6 +94,23 @@ height: 16px; } +.btn__shortcut { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 var(--space-2); + border-radius: var(--radius-sm); + border: 1px solid var(--border-secondary); + background-color: var(--bg-tertiary); + color: var(--text-tertiary); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + letter-spacing: 0.05em; + text-transform: uppercase; + line-height: 1; + min-height: 0; +} + /* Processor Toggles */ .processor-toggles { display: flex; diff --git a/vna_system/web_ui/static/css/layout.css b/vna_system/web_ui/static/css/layout.css index 731a460..3b22d60 100644 --- a/vna_system/web_ui/static/css/layout.css +++ b/vna_system/web_ui/static/css/layout.css @@ -397,6 +397,10 @@ body { display: none; } + .header__controls .btn__shortcut { + display: none; + } + .header__controls .btn { min-width: auto; padding: var(--space-2); diff --git a/vna_system/web_ui/static/js/main.js b/vna_system/web_ui/static/js/main.js index 8b77134..f96c21b 100644 --- a/vna_system/web_ui/static/js/main.js +++ b/vna_system/web_ui/static/js/main.js @@ -198,8 +198,25 @@ class VNADashboard { * Handle keyboard shortcuts */ handleKeyboardShortcuts(event) { + const activeElement = document.activeElement; + const activeTag = activeElement?.tagName?.toLowerCase(); + const isEditable = activeElement?.isContentEditable || + activeTag === 'input' || + activeTag === 'textarea' || + activeTag === 'select'; + const key = event.key?.toLowerCase(); + + if (!isEditable && !event.ctrlKey && !event.metaKey && !event.altKey && key === 's') { + if (event.repeat) { + return; + } + event.preventDefault(); + this.acquisition?.triggerSingleSweep?.(); + return; + } + // Ctrl/Cmd + Shift + R: Reconnect WebSocket - if ((event.ctrlKey || event.metaKey) && event.key === 'r' && event.shiftKey) { + if ((event.ctrlKey || event.metaKey) && key === 'r' && event.shiftKey) { event.preventDefault(); this.websocket.reconnect(); } diff --git a/vna_system/web_ui/templates/index.html b/vna_system/web_ui/templates/index.html index 95ba47d..b571be1 100644 --- a/vna_system/web_ui/templates/index.html +++ b/vna_system/web_ui/templates/index.html @@ -51,9 +51,10 @@ Стоп -