Refactor entry details display: replace DataTable with disabled input widgets for improved clarity and user experience.

This commit is contained in:
Philip Henning 2025-08-17 18:45:54 +02:00
parent 9a9161f28c
commit de5acd4dad
5 changed files with 120 additions and 68 deletions

View file

@ -331,7 +331,11 @@ class HostsManager:
# Remove the entry # Remove the entry
deleted_entry = hosts_file.entries.pop(index) deleted_entry = hosts_file.entries.pop(index)
canonical_hostname = deleted_entry.hostnames[0] if deleted_entry.hostnames else deleted_entry.ip_address canonical_hostname = (
deleted_entry.hostnames[0]
if deleted_entry.hostnames
else deleted_entry.ip_address
)
# Save the file immediately # Save the file immediately
save_success, save_message = self.save_hosts_file(hosts_file) save_success, save_message = self.save_hosts_file(hosts_file)

View file

@ -91,12 +91,30 @@ class HostsManagerApp(App):
# Right pane - entry details or edit form # Right pane - entry details or edit form
with Vertical(classes="common-pane right-pane") as right_pane: with Vertical(classes="common-pane right-pane") as right_pane:
right_pane.border_title = "Entry Details" right_pane.border_title = "Entry Details"
yield DataTable(
id="entry-details-table", # Details display form (disabled inputs)
show_header=False, with Vertical(id="entry-details-display"):
show_cursor=False, yield Label("IP Address:")
disabled=True, yield Input(
) placeholder="No entry selected",
id="details-ip-input",
disabled=True,
)
yield Label("Hostnames (comma-separated):")
yield Input(
placeholder="No entry selected",
id="details-hostname-input",
disabled=True,
)
yield Label("Comment:")
yield Input(
placeholder="No entry selected",
id="details-comment-input",
disabled=True,
)
yield Checkbox(
"Active", id="details-active-checkbox", disabled=True
)
# Edit form (initially hidden) # Edit form (initially hidden)
with Vertical(id="entry-edit-form", classes="hidden"): with Vertical(id="entry-edit-form", classes="hidden"):

View file

@ -5,7 +5,7 @@ This module handles the display and updating of entry details
and edit forms in the right pane. and edit forms in the right pane.
""" """
from textual.widgets import Input, Checkbox, DataTable from textual.widgets import Input, Checkbox
class DetailsHandler: class DetailsHandler:
@ -23,30 +23,41 @@ class DetailsHandler:
self.update_details_display() self.update_details_display()
def update_details_display(self) -> None: def update_details_display(self) -> None:
"""Update the details display using a DataTable with labeled rows.""" """Update the details display using disabled Input widgets."""
details_table = self.app.query_one("#entry-details-table", DataTable) details_display = self.app.query_one("#entry-details-display")
edit_form = self.app.query_one("#entry-edit-form") edit_form = self.app.query_one("#entry-edit-form")
# Show details table, hide edit form # Show details display, hide edit form
details_table.remove_class("hidden") details_display.remove_class("hidden")
edit_form.add_class("hidden") edit_form.add_class("hidden")
# Clear existing data # Get the input widgets
details_table.clear() ip_input = self.app.query_one("#details-ip-input", Input)
hostname_input = self.app.query_one("#details-hostname-input", Input)
comment_input = self.app.query_one("#details-comment-input", Input)
active_checkbox = self.app.query_one("#details-active-checkbox", Checkbox)
if not self.app.hosts_file.entries: if not self.app.hosts_file.entries:
# Show empty message in a single row # Show empty message
if not details_table.columns: ip_input.value = ""
details_table.add_column("Field", key="field") ip_input.placeholder = "No entries loaded"
details_table.add_row("No entries loaded") hostname_input.value = ""
hostname_input.placeholder = "No entries loaded"
comment_input.value = ""
comment_input.placeholder = "No entries loaded"
active_checkbox.value = False
return return
# Get visible entries to check if we need to adjust selection # Get visible entries to check if we need to adjust selection
visible_entries = self.app.table_handler.get_visible_entries() visible_entries = self.app.table_handler.get_visible_entries()
if not visible_entries: if not visible_entries:
if not details_table.columns: ip_input.value = ""
details_table.add_column("Field", key="field") ip_input.placeholder = "No visible entries"
details_table.add_row("No visible entries") hostname_input.value = ""
hostname_input.placeholder = "No visible entries"
comment_input.value = ""
comment_input.placeholder = "No visible entries"
active_checkbox.value = False
return return
# If default entries are hidden and selected_entry_index points to a hidden entry, # If default entries are hidden and selected_entry_index points to a hidden entry,
@ -73,36 +84,28 @@ class DetailsHandler:
entry = self.app.hosts_file.entries[self.app.selected_entry_index] entry = self.app.hosts_file.entries[self.app.selected_entry_index]
# Add columns for labeled rows (Field, Value) - only if not already present # Update the input widgets with entry data
if not details_table.columns: ip_input.value = entry.ip_address
details_table.add_column("Field", key="field") ip_input.placeholder = ""
details_table.add_column("Value", key="value") hostname_input.value = ", ".join(entry.hostnames)
hostname_input.placeholder = ""
comment_input.value = entry.comment or ""
comment_input.placeholder = "No comment"
active_checkbox.value = entry.is_active
# Add rows in the same order as edit form # For default entries, show warning in placeholder text
details_table.add_row("IP Address", entry.ip_address, key="ip")
details_table.add_row("Hostnames", ", ".join(entry.hostnames), key="hostnames")
details_table.add_row("Comment", entry.comment or "", key="comment")
details_table.add_row(
"Active", "Yes" if entry.is_active else "No", key="active"
)
# Add DNS name if present (not in edit form but good to show)
if entry.dns_name:
details_table.add_row("DNS Name", entry.dns_name, key="dns")
# Add notice for default system entries
if entry.is_default_entry(): if entry.is_default_entry():
details_table.add_row("", "", key="spacer") ip_input.placeholder = "⚠️ SYSTEM DEFAULT ENTRY - Cannot be modified"
details_table.add_row("⚠️ WARNING", "SYSTEM DEFAULT ENTRY", key="warning") hostname_input.placeholder = "⚠️ SYSTEM DEFAULT ENTRY - Cannot be modified"
details_table.add_row("Note", "This entry cannot be modified", key="note") comment_input.placeholder = "⚠️ SYSTEM DEFAULT ENTRY - Cannot be modified"
def update_edit_form(self) -> None: def update_edit_form(self) -> None:
"""Update the edit form with current entry values.""" """Update the edit form with current entry values."""
details_table = self.app.query_one("#entry-details-table", DataTable) details_display = self.app.query_one("#entry-details-display")
edit_form = self.app.query_one("#entry-edit-form") edit_form = self.app.query_one("#entry-edit-form")
# Hide details table, show edit form # Hide details display, show edit form
details_table.add_class("hidden") details_display.add_class("hidden")
edit_form.remove_class("hidden") edit_form.remove_class("hidden")
if not self.app.hosts_file.entries or self.app.selected_entry_index >= len( if not self.app.hosts_file.entries or self.app.selected_entry_index >= len(

View file

@ -156,16 +156,27 @@ class TestHostsManagerApp:
): ):
app = HostsManagerApp() app = HostsManagerApp()
# Mock the query_one method to return DataTable mock # Mock the query_one method to return disabled input widgets
mock_details_table = Mock() mock_details_display = Mock()
mock_details_table.columns = [] # Mock empty columns list
mock_edit_form = Mock() mock_edit_form = Mock()
mock_ip_input = Mock()
mock_hostname_input = Mock()
mock_comment_input = Mock()
mock_active_checkbox = Mock()
def mock_query_one(selector, widget_type=None): def mock_query_one(selector, widget_type=None):
if selector == "#entry-details-table": if selector == "#entry-details-display":
return mock_details_table return mock_details_display
elif selector == "#entry-edit-form": elif selector == "#entry-edit-form":
return mock_edit_form return mock_edit_form
elif selector == "#details-ip-input":
return mock_ip_input
elif selector == "#details-hostname-input":
return mock_hostname_input
elif selector == "#details-comment-input":
return mock_comment_input
elif selector == "#details-active-checkbox":
return mock_active_checkbox
return Mock() return Mock()
app.query_one = mock_query_one app.query_one = mock_query_one
@ -182,12 +193,13 @@ class TestHostsManagerApp:
app.update_entry_details() app.update_entry_details()
# Verify DataTable operations were called # Verify input widgets were updated with entry data
mock_details_table.remove_class.assert_called_with("hidden") mock_details_display.remove_class.assert_called_with("hidden")
mock_edit_form.add_class.assert_called_with("hidden") mock_edit_form.add_class.assert_called_with("hidden")
mock_details_table.clear.assert_called_once() assert mock_ip_input.value == "127.0.0.1"
mock_details_table.add_column.assert_called() assert mock_hostname_input.value == "localhost, local"
mock_details_table.add_row.assert_called() assert mock_comment_input.value == "Test comment"
assert mock_active_checkbox.value
def test_update_entry_details_no_entries(self): def test_update_entry_details_no_entries(self):
"""Test updating entry details with no entries.""" """Test updating entry details with no entries."""
@ -200,16 +212,27 @@ class TestHostsManagerApp:
): ):
app = HostsManagerApp() app = HostsManagerApp()
# Mock the query_one method to return DataTable mock # Mock the query_one method to return disabled input widgets
mock_details_table = Mock() mock_details_display = Mock()
mock_details_table.columns = [] # Mock empty columns list
mock_edit_form = Mock() mock_edit_form = Mock()
mock_ip_input = Mock()
mock_hostname_input = Mock()
mock_comment_input = Mock()
mock_active_checkbox = Mock()
def mock_query_one(selector, widget_type=None): def mock_query_one(selector, widget_type=None):
if selector == "#entry-details-table": if selector == "#entry-details-display":
return mock_details_table return mock_details_display
elif selector == "#entry-edit-form": elif selector == "#entry-edit-form":
return mock_edit_form return mock_edit_form
elif selector == "#details-ip-input":
return mock_ip_input
elif selector == "#details-hostname-input":
return mock_hostname_input
elif selector == "#details-comment-input":
return mock_comment_input
elif selector == "#details-active-checkbox":
return mock_active_checkbox
return Mock() return Mock()
app.query_one = mock_query_one app.query_one = mock_query_one
@ -217,12 +240,16 @@ class TestHostsManagerApp:
app.update_entry_details() app.update_entry_details()
# Verify DataTable operations were called for empty state # Verify widgets show empty state placeholders
mock_details_table.remove_class.assert_called_with("hidden") mock_details_display.remove_class.assert_called_with("hidden")
mock_edit_form.add_class.assert_called_with("hidden") mock_edit_form.add_class.assert_called_with("hidden")
mock_details_table.clear.assert_called_once() assert mock_ip_input.value == ""
mock_details_table.add_column.assert_called_with("Field", key="field") assert mock_ip_input.placeholder == "No entries loaded"
mock_details_table.add_row.assert_called_with("No entries loaded") assert mock_hostname_input.value == ""
assert mock_hostname_input.placeholder == "No entries loaded"
assert mock_comment_input.value == ""
assert mock_comment_input.placeholder == "No entries loaded"
assert not mock_active_checkbox.value
def test_update_status_default(self): def test_update_status_default(self):
"""Test status bar update with default information.""" """Test status bar update with default information."""