Refactor entry details display: replace DataTable with disabled input widgets for improved clarity and user experience.
This commit is contained in:
		
							parent
							
								
									9a9161f28c
								
							
						
					
					
						commit
						de5acd4dad
					
				
					 5 changed files with 120 additions and 68 deletions
				
			
		| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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:")
 | 
				
			||||||
 | 
					                    yield Input(
 | 
				
			||||||
 | 
					                        placeholder="No entry selected",
 | 
				
			||||||
 | 
					                        id="details-ip-input",
 | 
				
			||||||
                        disabled=True,
 | 
					                        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"):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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."""
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue