Coverage for src/lexigram/admin/actions/header_manager/manager.py: 0%
137 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:18 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:18 +0800
1"""
2Header action manager implementation.
4Provides the main HeaderActionManager class for managing header actions.
5"""
7from __future__ import annotations
9from collections.abc import Callable
10from typing import Any
12from lexigram.admin.actions.header_manager.actions import (
13 BASIC_ACTIONS,
14 BULK_ACTIONS,
15 IMPORT_EXPORT_ACTIONS,
16 UTILITY_ACTIONS,
17)
18from lexigram.admin.actions.header_manager.density import DensityManager
19from lexigram.admin.actions.header_manager.shortcuts import KeyboardShortcutManager
20from lexigram.admin.actions.header_manager.types import (
21 ColumnVisibilityConfig,
22 DensityConfig,
23 HeaderAction,
24 HeaderActionStyle,
25 IHeaderDataSource,
26 TableDensity,
27)
28from lexigram.admin.actions.header_manager.visibility import ColumnVisibilityManager
31class HeaderActionManager:
32 """Manages header actions for data tables."""
34 def __init__(
35 self,
36 data_source: IHeaderDataSource[Any] | None = None,
37 storage: Callable[[str, str], None] | None = None,
38 retriever: Callable[[str], str | None] | None = None,
39 ) -> None:
40 """Initialize the header action manager.
42 Args:
43 data_source: Data source for table operations
44 storage: Function to store user preferences
45 retriever: Function to retrieve user preferences
46 """
47 self.data_source = data_source
48 self._storage = storage
49 self._retriever = retriever
51 # Initialize sub-managers
52 self.visibility_manager = ColumnVisibilityManager(
53 ColumnVisibilityConfig(),
54 storage,
55 retriever,
56 )
57 self.density_manager = DensityManager(
58 DensityConfig(),
59 storage,
60 retriever,
61 )
62 self.shortcut_manager = KeyboardShortcutManager()
64 # Action collections
65 self._actions: dict[str, HeaderAction] = {}
66 self._action_groups: dict[str, list[str]] = {}
68 # Initialize with default actions
69 self._initialize_default_actions()
71 def _initialize_default_actions(self) -> None:
72 """Initialize default header actions."""
73 # Add basic actions
74 for action in BASIC_ACTIONS:
75 self.add_action(action)
77 # Add import/export actions
78 for action in IMPORT_EXPORT_ACTIONS:
79 self.add_action(action)
81 # Add utility actions
82 for action in UTILITY_ACTIONS:
83 self.add_action(action)
85 # Add bulk actions (initially hidden)
86 for action in BULK_ACTIONS:
87 self.add_action(action)
89 def add_action(self, action: HeaderAction) -> None:
90 """Add a header action."""
91 self._actions[action.name] = action
92 self.shortcut_manager.register_action(action)
94 def remove_action(self, action_name: str) -> None:
95 """Remove a header action."""
96 if action_name in self._actions:
97 action = self._actions[action_name]
98 if action.keyboard_shortcut:
99 self.shortcut_manager.unregister_action(action.keyboard_shortcut)
100 del self._actions[action_name]
102 def get_action(self, action_name: str) -> HeaderAction | None:
103 """Get a header action by name."""
104 return self._actions.get(action_name)
106 def get_all_actions(self) -> list[HeaderAction]:
107 """Get all header actions."""
108 return list(self._actions.values())
110 def get_visible_actions(self, position: str | None = None) -> list[HeaderAction]:
111 """Get actions that are currently visible."""
112 actions = [action for action in self._actions.values() if action.visible()]
113 if position:
114 actions = [action for action in actions if action.position == position]
115 return actions
117 async def execute_action(self, action_name: str) -> Any:
118 """Execute a header action."""
119 action = self.get_action(action_name)
120 if action and action.handler:
121 result = action.handler()
122 # If the result is a coroutine, await it
123 if hasattr(result, "__await__"):
124 return await result
125 return result
126 return None
128 def handle_keyboard_shortcut(self, shortcut: str) -> HeaderAction | None:
129 """Handle a keyboard shortcut."""
130 return self.shortcut_manager.get_action_for_shortcut(shortcut)
132 # Column visibility methods
133 def show_column(self, column: str) -> None:
134 """Show a column."""
135 self.visibility_manager.show_column(column)
137 def hide_column(self, column: str) -> None:
138 """Hide a column."""
139 self.visibility_manager.hide_column(column)
141 def toggle_column_visibility(self, column: str) -> None:
142 """Toggle column visibility."""
143 self.visibility_manager.toggle_column(column)
145 def get_visible_columns(self) -> set[str]:
146 """Get visible columns."""
147 return self.visibility_manager.visible_columns
149 def is_column_visible(self, column: str) -> bool:
150 """Check if column is visible."""
151 return self.visibility_manager.is_column_visible(column)
153 # Density methods
154 def set_table_density(self, density: TableDensity) -> None:
155 """Set table density."""
156 self.density_manager.set_density(density)
158 def cycle_table_density(self) -> TableDensity:
159 """Cycle to next density option."""
160 return self.density_manager.cycle_density()
162 def get_current_density(self) -> TableDensity:
163 """Get current table density."""
164 return self.density_manager.current_density
166 def get_density_css_class(self) -> str:
167 """Get CSS class for current density."""
168 return self.density_manager.get_css_class()
170 # Bulk action management
171 def update_bulk_actions_visibility(self, has_selection: bool) -> None:
172 """Update visibility of bulk actions based on selection."""
173 for action_name in ["bulk_delete", "bulk_edit"]:
174 action = self.get_action(action_name)
175 if action:
176 # Create new action with updated visibility
177 updated_action = HeaderAction(
178 name=action.name,
179 label=action.label,
180 handler=action.handler,
181 icon=action.icon,
182 style=action.style,
183 url=action.url,
184 method=action.method,
185 open_in_modal=action.open_in_modal,
186 keyboard_shortcut=action.keyboard_shortcut,
187 visible=lambda: has_selection,
188 disabled=action.disabled,
189 tooltip=action.tooltip,
190 badge=action.badge,
191 position=action.position,
192 metadata=action.metadata,
193 )
194 self.add_action(updated_action)
196 # Data source operations
197 async def refresh_data(self) -> list[Any]:
198 """Refresh table data."""
199 if self.data_source:
200 return await self.data_source.refresh()
201 return []
203 async def create_record(self, data: dict[str, Any]) -> Any:
204 """Create a new record."""
205 if self.data_source:
206 return await self.data_source.create(data)
207 return None
209 async def import_data(self, file_path: str, file_format: str = "csv") -> int:
210 """Import data from file."""
211 if self.data_source:
212 return await self.data_source.import_data(file_path, file_format)
213 return 0
215 async def export_data(self, file_format: str = "csv") -> str:
216 """Export data to file."""
217 if self.data_source:
218 return await self.data_source.export_all(file_format)
219 return ""
221 # Configuration methods
222 def configure_visibility(
223 self,
224 enabled: bool = True,
225 default_visible: list[str] | None = None,
226 always_visible: list[str] | None = None,
227 save_preference: bool = True,
228 ) -> None:
229 """Configure column visibility settings."""
230 config = ColumnVisibilityConfig(
231 enabled=enabled,
232 default_visible=default_visible or [],
233 always_visible=always_visible or [],
234 save_preference=save_preference,
235 )
236 self.visibility_manager = ColumnVisibilityManager(
237 config,
238 self._storage,
239 self._retriever,
240 )
242 def configure_density(
243 self,
244 enabled: bool = True,
245 default: TableDensity = TableDensity.NORMAL,
246 options: list[TableDensity] | None = None,
247 save_preference: bool = True,
248 ) -> None:
249 """Configure table density settings."""
250 config = DensityConfig(
251 enabled=enabled,
252 default=default,
253 options=options
254 or [
255 TableDensity.COMPACT,
256 TableDensity.NORMAL,
257 TableDensity.COMFORTABLE,
258 ],
259 save_preference=save_preference,
260 )
261 self.density_manager = DensityManager(
262 config,
263 self._storage,
264 self._retriever,
265 )
267 # Standard actions
268 def add_create_action(self, **kwargs) -> None:
269 """Add a create action."""
270 from lexigram.admin.actions.header_manager.actions import create_create_action
272 action = create_create_action(**kwargs)
273 self.add_action(action)
275 def add_import_action(self, **kwargs) -> None:
276 """Add an import action."""
277 from lexigram.admin.actions.header_manager.actions import create_import_action
279 action = create_import_action(**kwargs)
280 self.add_action(action)
282 def add_export_action(self, **kwargs) -> None:
283 """Add an export action."""
284 from lexigram.admin.actions.header_manager.actions import create_export_action
286 action = create_export_action(**kwargs)
287 if self.data_source:
288 action = HeaderAction(
289 name=action.name,
290 label=action.label,
291 handler=self.export_data,
292 icon=action.icon,
293 style=action.style,
294 url=action.url,
295 method=action.method,
296 open_in_modal=action.open_in_modal,
297 keyboard_shortcut=action.keyboard_shortcut,
298 visible=action.visible,
299 disabled=action.disabled,
300 tooltip=action.tooltip,
301 badge=action.badge,
302 position=action.position,
303 metadata=action.metadata,
304 )
305 self.add_action(action)
307 def add_refresh_action(self, **kwargs) -> None:
308 """Add a refresh action."""
309 from lexigram.admin.actions.header_manager.actions import create_refresh_action
311 action = create_refresh_action(**kwargs)
312 if self.data_source:
313 action = HeaderAction(
314 name=action.name,
315 label=action.label,
316 handler=self.refresh_data,
317 icon=action.icon,
318 style=action.style,
319 url=action.url,
320 method=action.method,
321 open_in_modal=action.open_in_modal,
322 keyboard_shortcut=action.keyboard_shortcut,
323 visible=action.visible,
324 disabled=action.disabled,
325 tooltip=action.tooltip,
326 badge=action.badge,
327 position=action.position,
328 metadata=action.metadata,
329 )
330 self.add_action(action)
332 def add_custom_action(
333 self,
334 name: str,
335 label: str,
336 handler: Callable[[], Any] | None = None,
337 **kwargs,
338 ) -> None:
339 """Add a custom header action."""
340 action = HeaderAction(
341 name=name,
342 label=label,
343 handler=handler,
344 icon=kwargs.get("icon"),
345 style=kwargs.get("style", HeaderActionStyle.SECONDARY),
346 url=kwargs.get("url"),
347 method=kwargs.get("method"), # type: ignore[arg-type]
348 open_in_modal=kwargs.get("open_in_modal", False),
349 keyboard_shortcut=kwargs.get("keyboard_shortcut"),
350 visible=kwargs.get("visible", lambda: True),
351 disabled=kwargs.get("disabled", lambda: False),
352 tooltip=kwargs.get("tooltip"),
353 badge=kwargs.get("badge"),
354 position=kwargs.get("position", "end"),
355 metadata=kwargs.get("metadata", {}),
356 )
357 self.add_action(action)
359 # Utility methods
360 def get_action_groups(self) -> dict[str, list[HeaderAction]]:
361 """Get actions grouped by position."""
362 groups: dict[str, list[HeaderAction]] = {"start": [], "end": []}
363 for action in self.get_visible_actions():
364 position = action.position
365 if position in groups:
366 groups[position].append(action)
367 return groups
369 def get_registered_shortcuts(self) -> dict[str, str]:
370 """Get all registered keyboard shortcuts."""
371 return self.shortcut_manager.get_registered_shortcuts()
373 def clear_all_actions(self) -> None:
374 """Clear all actions and shortcuts."""
375 self._actions.clear()
376 self.shortcut_manager.clear_all_shortcuts()