Fix TUI bugs: folder selection, filter stability, UI consistency
- Mail: Fix folder/account selector not triggering reload (use direct fetch instead of reactive reload_needed flag) - Tasks: Store all_projects/all_tags on mount so filters don't change when filtering; add OR search for multiple tags - Sync: Use rounded borders and border_title for sidebar/activity log - Calendar: Remove padding from mini-calendar, add rounded border and border_title to invites panel
This commit is contained in:
@@ -38,6 +38,7 @@ class InvitesPanel(Widget):
|
||||
height: auto;
|
||||
min-height: 3;
|
||||
padding: 0 1;
|
||||
border: round $primary;
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -71,6 +72,10 @@ class InvitesPanel(Widget):
|
||||
if invites:
|
||||
self.invites = invites
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Set border title on mount."""
|
||||
self.border_title = "Invites"
|
||||
|
||||
def _get_theme_color(self, color_name: str) -> str:
|
||||
"""Get a color from the current theme."""
|
||||
try:
|
||||
|
||||
@@ -62,7 +62,7 @@ class MonthCalendar(Widget):
|
||||
MonthCalendar {
|
||||
width: 24;
|
||||
height: auto;
|
||||
padding: 0 1;
|
||||
padding: 0;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@@ -216,16 +216,10 @@ class SyncDashboard(App):
|
||||
.sidebar {
|
||||
width: 30;
|
||||
height: 100%;
|
||||
border: solid $primary;
|
||||
border: round $primary;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
text-style: bold;
|
||||
padding: 1;
|
||||
background: $primary-darken-2;
|
||||
}
|
||||
|
||||
.countdown-container {
|
||||
height: 5;
|
||||
padding: 0 1;
|
||||
@@ -269,15 +263,10 @@ class SyncDashboard(App):
|
||||
|
||||
.log-container {
|
||||
height: 1fr;
|
||||
border: solid $primary;
|
||||
border: round $primary;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.log-title {
|
||||
padding: 0 1;
|
||||
background: $primary-darken-2;
|
||||
}
|
||||
|
||||
ListView {
|
||||
height: 1fr;
|
||||
}
|
||||
@@ -338,8 +327,7 @@ class SyncDashboard(App):
|
||||
|
||||
with Horizontal(classes="dashboard"):
|
||||
# Sidebar with task list
|
||||
with Vertical(classes="sidebar"):
|
||||
yield Static("Tasks", classes="sidebar-title")
|
||||
with Vertical(classes="sidebar", id="tasks-sidebar"):
|
||||
yield ListView(
|
||||
# Stage 1: Sync local changes to server
|
||||
TaskListItem(
|
||||
@@ -416,8 +404,7 @@ class SyncDashboard(App):
|
||||
yield Static("0%", id="progress-percent")
|
||||
|
||||
# Log for selected task
|
||||
with Vertical(classes="log-container"):
|
||||
yield Static("Activity Log", classes="log-title")
|
||||
with Vertical(classes="log-container", id="log-container"):
|
||||
yield Log(id="task-log")
|
||||
|
||||
yield Footer()
|
||||
@@ -427,6 +414,13 @@ class SyncDashboard(App):
|
||||
# Set theme from shared config
|
||||
self.theme = get_theme_name()
|
||||
|
||||
# Set border titles
|
||||
try:
|
||||
self.query_one("#tasks-sidebar").border_title = "Tasks"
|
||||
self.query_one("#log-container").border_title = "Activity Log"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Store references to task items
|
||||
task_list = self.query_one("#task-list", ListView)
|
||||
for item in task_list.children:
|
||||
|
||||
@@ -353,7 +353,9 @@ class EmailViewerApp(App):
|
||||
self.current_message_id = 0
|
||||
self.current_message_index = 0
|
||||
self.selected_messages.clear()
|
||||
self.reload_needed = True
|
||||
self.search_query = "" # Clear search when switching folders
|
||||
# Directly fetch instead of relying on reload_needed watcher
|
||||
self.fetch_envelopes()
|
||||
except Exception as e:
|
||||
logging.error(f"Error selecting folder: {e}")
|
||||
|
||||
@@ -372,9 +374,11 @@ class EmailViewerApp(App):
|
||||
self.current_message_id = 0
|
||||
self.current_message_index = 0
|
||||
self.selected_messages.clear()
|
||||
self.search_query = "" # Clear search when switching accounts
|
||||
# Refresh folders for new account
|
||||
self.fetch_folders()
|
||||
self.reload_needed = True
|
||||
# Directly fetch instead of relying on reload_needed watcher
|
||||
self.fetch_envelopes()
|
||||
except Exception as e:
|
||||
logging.error(f"Error selecting account: {e}")
|
||||
|
||||
|
||||
@@ -197,6 +197,8 @@ class TasksApp(App):
|
||||
self.tasks = []
|
||||
self.projects = []
|
||||
self.tags = []
|
||||
self.all_projects = [] # Stable list of all projects (not filtered)
|
||||
self.all_tags = [] # Stable list of all tags (not filtered)
|
||||
self.current_project_filter = None
|
||||
self.current_tag_filters = []
|
||||
self.current_sort_column = "priority"
|
||||
@@ -261,7 +263,10 @@ class TasksApp(App):
|
||||
height = max(10, min(90, height))
|
||||
notes_pane.styles.height = f"{height}%"
|
||||
|
||||
# Load tasks (this will also update the sidebar)
|
||||
# Load ALL projects and tags once for stable sidebar
|
||||
self._load_all_filters()
|
||||
|
||||
# Load tasks (filtered by current filters)
|
||||
self.load_tasks()
|
||||
|
||||
def _setup_columns(self, table: DataTable, columns: list[str]) -> None:
|
||||
@@ -375,32 +380,58 @@ class TasksApp(App):
|
||||
if not self.backend:
|
||||
return
|
||||
|
||||
# Get tasks with current filters
|
||||
self.tasks = self.backend.get_tasks(
|
||||
project=self.current_project_filter,
|
||||
tags=self.current_tag_filters if self.current_tag_filters else None,
|
||||
)
|
||||
# Get ALL tasks first (unfiltered)
|
||||
all_tasks = self.backend.get_tasks()
|
||||
|
||||
# Apply client-side filtering for OR logic
|
||||
self.tasks = self._filter_tasks(all_tasks)
|
||||
|
||||
# Sort tasks
|
||||
self._sort_tasks()
|
||||
|
||||
# Also load projects and tags for filtering
|
||||
self.projects = self.backend.get_projects()
|
||||
self.tags = self.backend.get_tags()
|
||||
|
||||
# Update sidebar with available filters
|
||||
self._update_sidebar()
|
||||
|
||||
# Update table
|
||||
self._update_table()
|
||||
|
||||
def _load_all_filters(self) -> None:
|
||||
"""Load all projects and tags once for stable sidebar."""
|
||||
if not self.backend:
|
||||
return
|
||||
|
||||
self.all_projects = self.backend.get_projects()
|
||||
self.all_tags = self.backend.get_tags()
|
||||
|
||||
# Update sidebar with stable filter options
|
||||
self._update_sidebar()
|
||||
|
||||
def _filter_tasks(self, tasks: list[Task]) -> list[Task]:
|
||||
"""Filter tasks by current project and tag filters using OR logic.
|
||||
|
||||
- If project filter is set, only show tasks from that project
|
||||
- If tag filters are set, show tasks that have ANY of the selected tags (OR)
|
||||
"""
|
||||
filtered = tasks
|
||||
|
||||
# Filter by project (single project filter is AND)
|
||||
if self.current_project_filter:
|
||||
filtered = [t for t in filtered if t.project == self.current_project_filter]
|
||||
|
||||
# Filter by tags using OR logic - show tasks with ANY of the selected tags
|
||||
if self.current_tag_filters:
|
||||
filtered = [
|
||||
t
|
||||
for t in filtered
|
||||
if any(tag in t.tags for tag in self.current_tag_filters)
|
||||
]
|
||||
|
||||
return filtered
|
||||
|
||||
def _update_sidebar(self) -> None:
|
||||
"""Update the filter sidebar with current projects and tags."""
|
||||
"""Update the filter sidebar with all available projects and tags."""
|
||||
try:
|
||||
sidebar = self.query_one("#sidebar", FilterSidebar)
|
||||
# Convert projects to (name, count) tuples
|
||||
project_data = [(p.name, p.task_count) for p in self.projects if p.name]
|
||||
sidebar.update_filters(projects=project_data, tags=self.tags)
|
||||
# Use stable all_projects/all_tags, not filtered ones
|
||||
project_data = [(p.name, p.task_count) for p in self.all_projects if p.name]
|
||||
sidebar.update_filters(projects=project_data, tags=self.all_tags)
|
||||
except Exception:
|
||||
pass # Sidebar may not be mounted yet
|
||||
|
||||
|
||||
Reference in New Issue
Block a user