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;
|
height: auto;
|
||||||
min-height: 3;
|
min-height: 3;
|
||||||
padding: 0 1;
|
padding: 0 1;
|
||||||
|
border: round $primary;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -71,6 +72,10 @@ class InvitesPanel(Widget):
|
|||||||
if invites:
|
if invites:
|
||||||
self.invites = 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:
|
def _get_theme_color(self, color_name: str) -> str:
|
||||||
"""Get a color from the current theme."""
|
"""Get a color from the current theme."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class MonthCalendar(Widget):
|
|||||||
MonthCalendar {
|
MonthCalendar {
|
||||||
width: 24;
|
width: 24;
|
||||||
height: auto;
|
height: auto;
|
||||||
padding: 0 1;
|
padding: 0;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -216,16 +216,10 @@ class SyncDashboard(App):
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
width: 30;
|
width: 30;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: solid $primary;
|
border: round $primary;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-title {
|
|
||||||
text-style: bold;
|
|
||||||
padding: 1;
|
|
||||||
background: $primary-darken-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.countdown-container {
|
.countdown-container {
|
||||||
height: 5;
|
height: 5;
|
||||||
padding: 0 1;
|
padding: 0 1;
|
||||||
@@ -269,15 +263,10 @@ class SyncDashboard(App):
|
|||||||
|
|
||||||
.log-container {
|
.log-container {
|
||||||
height: 1fr;
|
height: 1fr;
|
||||||
border: solid $primary;
|
border: round $primary;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-title {
|
|
||||||
padding: 0 1;
|
|
||||||
background: $primary-darken-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
height: 1fr;
|
height: 1fr;
|
||||||
}
|
}
|
||||||
@@ -338,8 +327,7 @@ class SyncDashboard(App):
|
|||||||
|
|
||||||
with Horizontal(classes="dashboard"):
|
with Horizontal(classes="dashboard"):
|
||||||
# Sidebar with task list
|
# Sidebar with task list
|
||||||
with Vertical(classes="sidebar"):
|
with Vertical(classes="sidebar", id="tasks-sidebar"):
|
||||||
yield Static("Tasks", classes="sidebar-title")
|
|
||||||
yield ListView(
|
yield ListView(
|
||||||
# Stage 1: Sync local changes to server
|
# Stage 1: Sync local changes to server
|
||||||
TaskListItem(
|
TaskListItem(
|
||||||
@@ -416,8 +404,7 @@ class SyncDashboard(App):
|
|||||||
yield Static("0%", id="progress-percent")
|
yield Static("0%", id="progress-percent")
|
||||||
|
|
||||||
# Log for selected task
|
# Log for selected task
|
||||||
with Vertical(classes="log-container"):
|
with Vertical(classes="log-container", id="log-container"):
|
||||||
yield Static("Activity Log", classes="log-title")
|
|
||||||
yield Log(id="task-log")
|
yield Log(id="task-log")
|
||||||
|
|
||||||
yield Footer()
|
yield Footer()
|
||||||
@@ -427,6 +414,13 @@ class SyncDashboard(App):
|
|||||||
# Set theme from shared config
|
# Set theme from shared config
|
||||||
self.theme = get_theme_name()
|
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
|
# Store references to task items
|
||||||
task_list = self.query_one("#task-list", ListView)
|
task_list = self.query_one("#task-list", ListView)
|
||||||
for item in task_list.children:
|
for item in task_list.children:
|
||||||
|
|||||||
@@ -353,7 +353,9 @@ class EmailViewerApp(App):
|
|||||||
self.current_message_id = 0
|
self.current_message_id = 0
|
||||||
self.current_message_index = 0
|
self.current_message_index = 0
|
||||||
self.selected_messages.clear()
|
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:
|
except Exception as e:
|
||||||
logging.error(f"Error selecting folder: {e}")
|
logging.error(f"Error selecting folder: {e}")
|
||||||
|
|
||||||
@@ -372,9 +374,11 @@ class EmailViewerApp(App):
|
|||||||
self.current_message_id = 0
|
self.current_message_id = 0
|
||||||
self.current_message_index = 0
|
self.current_message_index = 0
|
||||||
self.selected_messages.clear()
|
self.selected_messages.clear()
|
||||||
|
self.search_query = "" # Clear search when switching accounts
|
||||||
# Refresh folders for new account
|
# Refresh folders for new account
|
||||||
self.fetch_folders()
|
self.fetch_folders()
|
||||||
self.reload_needed = True
|
# Directly fetch instead of relying on reload_needed watcher
|
||||||
|
self.fetch_envelopes()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error selecting account: {e}")
|
logging.error(f"Error selecting account: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ class TasksApp(App):
|
|||||||
self.tasks = []
|
self.tasks = []
|
||||||
self.projects = []
|
self.projects = []
|
||||||
self.tags = []
|
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_project_filter = None
|
||||||
self.current_tag_filters = []
|
self.current_tag_filters = []
|
||||||
self.current_sort_column = "priority"
|
self.current_sort_column = "priority"
|
||||||
@@ -261,7 +263,10 @@ class TasksApp(App):
|
|||||||
height = max(10, min(90, height))
|
height = max(10, min(90, height))
|
||||||
notes_pane.styles.height = f"{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()
|
self.load_tasks()
|
||||||
|
|
||||||
def _setup_columns(self, table: DataTable, columns: list[str]) -> None:
|
def _setup_columns(self, table: DataTable, columns: list[str]) -> None:
|
||||||
@@ -375,32 +380,58 @@ class TasksApp(App):
|
|||||||
if not self.backend:
|
if not self.backend:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get tasks with current filters
|
# Get ALL tasks first (unfiltered)
|
||||||
self.tasks = self.backend.get_tasks(
|
all_tasks = self.backend.get_tasks()
|
||||||
project=self.current_project_filter,
|
|
||||||
tags=self.current_tag_filters if self.current_tag_filters else None,
|
# Apply client-side filtering for OR logic
|
||||||
)
|
self.tasks = self._filter_tasks(all_tasks)
|
||||||
|
|
||||||
# Sort tasks
|
# Sort tasks
|
||||||
self._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
|
# Update table
|
||||||
self._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:
|
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:
|
try:
|
||||||
sidebar = self.query_one("#sidebar", FilterSidebar)
|
sidebar = self.query_one("#sidebar", FilterSidebar)
|
||||||
# Convert projects to (name, count) tuples
|
# Use stable all_projects/all_tags, not filtered ones
|
||||||
project_data = [(p.name, p.task_count) for p in self.projects if p.name]
|
project_data = [(p.name, p.task_count) for p in self.all_projects if p.name]
|
||||||
sidebar.update_filters(projects=project_data, tags=self.tags)
|
sidebar.update_filters(projects=project_data, tags=self.all_tags)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # Sidebar may not be mounted yet
|
pass # Sidebar may not be mounted yet
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user