diff --git a/src/constants.py b/src/constants.py index f1f8afc..2f3192e 100644 --- a/src/constants.py +++ b/src/constants.py @@ -17,6 +17,34 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +# UI Layout Constants +UI_PANE_REQUEST_RESPONSE_POSITION = 510 # Initial position for request/response split +UI_PANE_RESPONSE_DETAILS_POSITION = 400 # Initial position for response body/headers split +UI_PANE_RESULTS_PANEL_POSITION = 120 # Initial position for script results panel +UI_PANE_SCRIPTS_POSITION = 300 # Initial position for scripts tab panes +UI_SCRIPT_RESULTS_PANEL_HEIGHT = 150 # Height reserved for script results panel +UI_TOAST_TIMEOUT_SECONDS = 3 # Duration to show toast notifications + +# Debounce/Delay Timeouts (milliseconds) +DEBOUNCE_VARIABLE_INDICATORS_MS = 500 # Delay before updating variable indicators +DELAY_TAB_CREATION_MS = 50 # Delay before creating new tab after last one closes + +# Data Display Limits +DISPLAY_VARIABLE_MAX_LENGTH = 50 # Max characters to display for variable values +DISPLAY_VARIABLE_TRUNCATE_SUFFIX = "..." # Suffix for truncated variable values +DISPLAY_URL_NAME_MAX_LENGTH = 30 # Max characters for URL in generated tab names + +# Data Storage Limits +HISTORY_MAX_ENTRIES = 100 # Maximum number of history entries to keep +PROJECT_NAME_MAX_LENGTH = 100 # Maximum length for project names +REQUEST_NAME_MAX_LENGTH = 200 # Maximum length for request names + +# HTTP Client Settings +HTTP_DEFAULT_TIMEOUT_SECONDS = 30 # Default timeout for HTTP requests + +# Script Execution Settings +SCRIPT_EXECUTION_TIMEOUT_SECONDS = 5 # Maximum execution time for scripts + # 36 symbolic icons for projects (6x6 grid) PROJECT_ICONS = [ # Row 1: Folders & Organization diff --git a/src/history_manager.py b/src/history_manager.py index f8f584c..5c1995c 100644 --- a/src/history_manager.py +++ b/src/history_manager.py @@ -26,6 +26,7 @@ import gi gi.require_version('GLib', '2.0') from gi.repository import GLib from .models import HistoryEntry +from .constants import HISTORY_MAX_ENTRIES logger = logging.getLogger(__name__) @@ -77,8 +78,8 @@ class HistoryManager: entries = self.load_history() entries.insert(0, entry) # Most recent first - # Limit history size to 100 entries - entries = entries[:100] + # Limit history size + entries = entries[:HISTORY_MAX_ENTRIES] self.save_history(entries) diff --git a/src/request_tab_widget.py b/src/request_tab_widget.py index 2042449..469ee55 100644 --- a/src/request_tab_widget.py +++ b/src/request_tab_widget.py @@ -24,6 +24,16 @@ from typing import Optional, Set, Dict, List import logging from .models import HttpRequest, HttpResponse from .widgets.header_row import HeaderRow +from .constants import ( + UI_PANE_REQUEST_RESPONSE_POSITION, + UI_PANE_RESPONSE_DETAILS_POSITION, + UI_PANE_RESULTS_PANEL_POSITION, + UI_PANE_SCRIPTS_POSITION, + UI_SCRIPT_RESULTS_PANEL_HEIGHT, + DEBOUNCE_VARIABLE_INDICATORS_MS, + DISPLAY_VARIABLE_MAX_LENGTH, + DISPLAY_VARIABLE_TRUNCATE_SUFFIX, +) import json import xml.dom.minidom @@ -118,7 +128,7 @@ class RequestTabWidget(Gtk.Box): # Horizontal Split: Request | Response split_pane = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL) split_pane.set_vexpand(True) - split_pane.set_position(510) + split_pane.set_position(UI_PANE_REQUEST_RESPONSE_POSITION) split_pane.set_shrink_start_child(False) split_pane.set_shrink_end_child(False) split_pane.set_resize_start_child(True) @@ -164,7 +174,7 @@ class RequestTabWidget(Gtk.Box): # Create a vertical paned for response stack and result panels self.response_main_paned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL) self.response_main_paned.set_vexpand(True) - self.response_main_paned.set_position(400) + self.response_main_paned.set_position(UI_PANE_RESPONSE_DETAILS_POSITION) self.response_main_paned.set_shrink_start_child(False) # Don't allow results panels to shrink below their minimum size self.response_main_paned.set_shrink_end_child(False) @@ -228,7 +238,7 @@ class RequestTabWidget(Gtk.Box): # Create a second paned for preprocessing and script results self.results_paned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL) self.results_paned.set_vexpand(True) - self.results_paned.set_position(120) + self.results_paned.set_position(UI_PANE_RESULTS_PANEL_POSITION) # Don't allow children to shrink below their minimum size self.results_paned.set_shrink_start_child(False) self.results_paned.set_shrink_end_child(False) @@ -474,7 +484,7 @@ class RequestTabWidget(Gtk.Box): # Vertical paned to separate preprocessing and postprocessing paned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL) - paned.set_position(300) + paned.set_position(UI_PANE_SCRIPTS_POSITION) paned.set_vexpand(True) # Preprocessing Section @@ -942,7 +952,7 @@ class RequestTabWidget(Gtk.Box): output_text += "\n--- Variables Set ---" for var_name, var_value in script_result.variable_updates.items(): # Truncate long values for display - display_value = var_value if len(var_value) <= 50 else var_value[:47] + "..." + display_value = var_value if len(var_value) <= DISPLAY_VARIABLE_MAX_LENGTH else var_value[:DISPLAY_VARIABLE_MAX_LENGTH - 3] + DISPLAY_VARIABLE_TRUNCATE_SUFFIX output_text += f"\n>>> {var_name} = '{display_value}'" # Append warnings to output @@ -983,14 +993,14 @@ class RequestTabWidget(Gtk.Box): main_height = self.response_main_paned.get_height() if main_height > 200: # Set position to show ~150px of results at the bottom - self.response_main_paned.set_position(main_height - 150) + self.response_main_paned.set_position(main_height - UI_SCRIPT_RESULTS_PANEL_HEIGHT) # Adjust results paned to show script output (bottom panel) results_height = self.results_paned.get_height() if results_height > 150: # If preprocessing is visible, share space if self.preprocessing_results_container.get_visible(): - self.results_paned.set_position(120) # Give preprocessing minimal space + self.results_paned.set_position(UI_PANE_RESULTS_PANEL_POSITION) # Give preprocessing minimal space # If only script results, the paned will show it automatically return False # Don't repeat @@ -1023,7 +1033,7 @@ class RequestTabWidget(Gtk.Box): output_text += "\n--- Variables Set ---" for var_name, var_value in preprocessing_result.variable_updates.items(): # Truncate long values for display - display_value = var_value if len(var_value) <= 50 else var_value[:47] + "..." + display_value = var_value if len(var_value) <= DISPLAY_VARIABLE_MAX_LENGTH else var_value[:DISPLAY_VARIABLE_MAX_LENGTH - 3] + DISPLAY_VARIABLE_TRUNCATE_SUFFIX output_text += f"\n>>> {var_name} = '{display_value}'" # Append warnings to output @@ -1071,7 +1081,7 @@ class RequestTabWidget(Gtk.Box): main_height = self.response_main_paned.get_height() if main_height > 200: # Set position to show ~150px of results at the bottom - self.response_main_paned.set_position(main_height - 150) + self.response_main_paned.set_position(main_height - UI_SCRIPT_RESULTS_PANEL_HEIGHT) # Adjust results paned to show preprocessing output (top panel) results_height = self.results_paned.get_height() @@ -1283,7 +1293,7 @@ class RequestTabWidget(Gtk.Box): GLib.source_remove(self._update_indicators_timeout_id) # Schedule new update after 500ms - self._update_indicators_timeout_id = GLib.timeout_add(500, self._update_variable_indicators_timeout) + self._update_indicators_timeout_id = GLib.timeout_add(DEBOUNCE_VARIABLE_INDICATORS_MS, self._update_variable_indicators_timeout) def _update_variable_indicators_timeout(self): """Timeout callback for updating indicators.""" diff --git a/src/script_executor.py b/src/script_executor.py index 36a1956..8f69e83 100644 --- a/src/script_executor.py +++ b/src/script_executor.py @@ -24,6 +24,7 @@ import re from pathlib import Path from typing import Tuple, Optional, Dict, List, TYPE_CHECKING from dataclasses import dataclass, field +from .constants import SCRIPT_EXECUTION_TIMEOUT_SECONDS if TYPE_CHECKING: from .project_manager import ProjectManager @@ -52,7 +53,7 @@ class ScriptContext: class ScriptExecutor: """Executes JavaScript code using gjs (GNOME JavaScript).""" - TIMEOUT_SECONDS = 5 + TIMEOUT_SECONDS = SCRIPT_EXECUTION_TIMEOUT_SECONDS @staticmethod def execute_postprocessing_script( diff --git a/src/window.py b/src/window.py index 4c81a01..3de4bc0 100644 --- a/src/window.py +++ b/src/window.py @@ -23,6 +23,13 @@ from gi.repository import Adw, Gtk, GLib, Gio, GtkSource from typing import Dict, Optional import logging from .models import HttpRequest, HttpResponse, HistoryEntry, RequestTab +from .constants import ( + UI_TOAST_TIMEOUT_SECONDS, + DELAY_TAB_CREATION_MS, + DISPLAY_URL_NAME_MAX_LENGTH, + PROJECT_NAME_MAX_LENGTH, + REQUEST_NAME_MAX_LENGTH, +) from .http_client import HttpClient from .history_manager import HistoryManager from .project_manager import ProjectManager @@ -239,7 +246,7 @@ class RosterWindow(Adw.ApplicationWindow): remaining_tabs = len(self.page_to_tab) if remaining_tabs == 0: # Schedule creating exactly one new tab - GLib.timeout_add(50, self._create_new_tab_once) + GLib.timeout_add(DELAY_TAB_CREATION_MS, self._create_new_tab_once) # Close the page tab_view.close_page_finish(page, True) @@ -601,7 +608,7 @@ class RosterWindow(Adw.ApplicationWindow): # Generate name from method and URL url_parts = request.url.split('/') url_name = url_parts[-1] if url_parts else request.url - name = f"{request.method} {url_name[:30]}" if url_name else f"{request.method} Request" + name = f"{request.method} {url_name[:DISPLAY_URL_NAME_MAX_LENGTH]}" if url_name else f"{request.method} Request" # Check if current tab is an empty "New Request" if self._is_empty_new_request_tab(): @@ -756,7 +763,7 @@ class RosterWindow(Adw.ApplicationWindow): """Show a toast notification.""" toast = Adw.Toast() toast.set_title(message) - toast.set_timeout(3) + toast.set_timeout(UI_TOAST_TIMEOUT_SECONDS) self.toast_overlay.add_toast(toast) # Project Management Methods @@ -775,9 +782,9 @@ class RosterWindow(Adw.ApplicationWindow): if not name or not name.strip(): return False, "Project name cannot be empty" - # Check length (max 100 characters) - if len(name) > 100: - return False, "Project name is too long (max 100 characters)" + # Check length + if len(name) > PROJECT_NAME_MAX_LENGTH: + return False, f"Project name is too long (max {PROJECT_NAME_MAX_LENGTH} characters)" # Check for invalid characters (file system unsafe characters) invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|', '\0'] @@ -805,9 +812,9 @@ class RosterWindow(Adw.ApplicationWindow): if not name or not name.strip(): return False, "Request name cannot be empty" - # Check length (max 200 characters) - if len(name) > 200: - return False, "Request name is too long (max 200 characters)" + # Check length + if len(name) > REQUEST_NAME_MAX_LENGTH: + return False, f"Request name is too long (max {REQUEST_NAME_MAX_LENGTH} characters)" # Check for invalid characters (file system unsafe characters) invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|', '\0']