I can show and hide the columns comanger

This commit is contained in:
2026-01-25 11:29:18 +01:00
parent 7f3e6270a2
commit 3abfab8e97
8 changed files with 656 additions and 228 deletions

View File

@@ -14,7 +14,7 @@ from myfasthtml.controls.CycleStateControl import CycleStateControl
from myfasthtml.controls.DataGridColumnsManager import DataGridColumnsManager
from myfasthtml.controls.DataGridQuery import DataGridQuery, DG_QUERY_FILTER
from myfasthtml.controls.Mouse import Mouse
from myfasthtml.controls.Panel import Panel
from myfasthtml.controls.Panel import Panel, PanelConf
from myfasthtml.controls.datagrid_objects import DataGridColumnState, DataGridRowState, \
DatagridSelectionState, DataGridHeaderFooterConf, DatagridEditionState
from myfasthtml.controls.helpers import mk
@@ -26,6 +26,7 @@ from myfasthtml.core.optimized_ft import OptimizedDiv
from myfasthtml.core.utils import make_safe_id
from myfasthtml.icons.carbon import row, column, grid
from myfasthtml.icons.fluent import checkbox_unchecked16_regular
from myfasthtml.icons.fluent_p1 import settings16_regular
from myfasthtml.icons.fluent_p2 import checkbox_checked16_regular
# OPTIMIZATION: Pre-compiled regex to detect HTML special characters
@@ -129,6 +130,13 @@ class Commands(BaseCommands):
self._owner,
self._owner.on_click
).htmx(target=f"#tsm_{self._id}")
def toggle_columns_manager(self):
return Command("ToggleColumnsManager",
"Toggle Columns Manager",
self._owner,
self._owner.toggle_columns_manager
).htmx(target=None)
class DataGrid(MultipleInstance):
@@ -139,7 +147,10 @@ class DataGrid(MultipleInstance):
self.commands = Commands(self)
self.init_from_dataframe(self._state.ne_df, init_state=False) # state comes from DatagridState
self._panel = Panel(self, _id="-panel")
# add Panel
self._panel = Panel(self, conf=PanelConf(right_title="Columns", show_display_right=False), _id="-panel")
self._panel.set_side_visible("right", False) # the right Panel always starts closed
self.bind_command("ToggleColumnsManager", self._panel.commands.toggle_side("right"))
# add DataGridQuery
self._datagrid_filter = DataGridQuery(self)
@@ -362,6 +373,10 @@ class DataGrid(MultipleInstance):
self._state.save()
return self.render_partial()
def toggle_columns_manager(self):
logger.debug(f"toggle_columns_manager")
self._panel.set_right(self._columns_manager)
def mk_headers(self):
resize_cmd = self.commands.set_column_width()
move_cmd = self.commands.move_column()
@@ -649,7 +664,7 @@ class DataGrid(MultipleInstance):
Div(self._datagrid_filter,
Div(
self._selection_mode_selector,
self._columns_manager,
mk.icon(settings16_regular, command=self.commands.toggle_columns_manager(), tooltip="Show sidebar"),
cls="flex"),
cls="flex items-center justify-between mb-2"),
self._panel.set_main(self.mk_table()),

View File

@@ -1,17 +1,17 @@
from fasthtml.components import *
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.controls.Search import Search
from myfasthtml.controls.datagrid_objects import DataGridColumnState
from myfasthtml.controls.helpers import mk
from myfasthtml.icons.fluent_p1 import settings16_regular
from myfasthtml.core.instances import MultipleInstance
class DataGridColumnsManager(Dropdown):
class DataGridColumnsManager(MultipleInstance):
def __init__(self, parent, _id=None):
super().__init__(parent, _id=_id, align="right")
self.button = mk.icon(settings16_regular)
self.content = self.create_content()
super().__init__(parent, _id=_id)
@property
def columns(self):
return self._parent._state.columns
def mk_column(self, col_def: DataGridColumnState):
return Div(
@@ -20,14 +20,14 @@ class DataGridColumnsManager(Dropdown):
cls="flex mb-1",
)
def create_content(self):
def render(self):
return Search(self,
items_names="Columns",
items=self.columns,
get_attr=lambda x: x.col_id,
template=self.mk_column
template=self.mk_column,
max_height=None
)
@property
def columns(self):
return self._parent._state.columns
def __ft__(self):
return self.render()

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Literal
from typing import Literal, Optional
from fasthtml.components import Div
from fasthtml.xtend import Script
@@ -42,6 +42,12 @@ class PanelIds:
class PanelConf:
left: bool = False
right: bool = True
left_title: str = "Left"
right_title: str = "Right"
show_left_title: bool = True
show_right_title: bool = True
show_display_left: bool = True
show_display_right: bool = True
class PanelState(DbObject):
@@ -55,12 +61,19 @@ class PanelState(DbObject):
class Commands(BaseCommands):
def toggle_side(self, side: Literal["left", "right"], visible: bool = None):
def set_side_visible(self, side: Literal["left", "right"], visible: bool = None):
return Command("TogglePanelSide",
f"Toggle {side} side panel",
self._owner,
self._owner.set_side_visible,
args=[side, visible]).htmx(target=f"#{self._owner.get_ids().panel(side)}")
def toggle_side(self, side: Literal["left", "right"]):
return Command("TogglePanelSide",
f"Toggle {side} side panel",
self._owner,
self._owner.toggle_side,
args=[side, visible]).htmx(target=f"#{self._owner.get_ids().panel(side)}")
args=[side]).htmx(target=f"#{self._owner.get_ids().panel(side)}")
def update_side_width(self, side: Literal["left", "right"]):
"""
@@ -89,7 +102,7 @@ class Panel(MultipleInstance):
the panel with appropriate HTML elements and JavaScript for interactivity.
"""
def __init__(self, parent, conf=None, _id=None):
def __init__(self, parent, conf: Optional[PanelConf] = None, _id=None):
super().__init__(parent, _id=_id)
self.conf = conf or PanelConf()
self.commands = Commands(self)
@@ -110,7 +123,7 @@ class Panel(MultipleInstance):
return self._mk_panel(side)
def toggle_side(self, side, visible):
def set_side_visible(self, side, visible):
if side == "left":
self._state.left_visible = visible
else:
@@ -118,6 +131,10 @@ class Panel(MultipleInstance):
return self._mk_panel(side), self._mk_show_icon(side)
def toggle_side(self, side):
current_visible = self._state.left_visible if side == "left" else self._state.right_visible
return self.set_side_visible(side, not current_visible)
def set_main(self, main):
self._main = main
return self
@@ -134,45 +151,75 @@ class Panel(MultipleInstance):
enabled = self.conf.left if side == "left" else self.conf.right
if not enabled:
return None
visible = self._state.left_visible if side == "left" else self._state.right_visible
content = self._right if side == "right" else self._left
show_title = self.conf.show_left_title if side == "left" else self.conf.show_right_title
title = self.conf.left_title if side == "left" else self.conf.right_title
resizer = Div(
cls=f"mf-resizer mf-resizer-{side}",
data_command_id=self.commands.update_side_width(side).id,
data_side=side
)
hide_icon = mk.icon(
subtract20_regular,
size=20,
command=self.commands.toggle_side(side, False),
command=self.commands.set_side_visible(side, False),
cls="mf-panel-hide-icon"
)
panel_cls = f"mf-panel-{side}"
if not visible:
panel_cls += " mf-hidden"
if show_title:
panel_cls += " mf-panel-with-title"
# Left panel: content then resizer (resizer on the right)
# Right panel: resizer then content (resizer on the left)
if side == "left":
return Div(
if show_title:
header = Div(
Div(title),
hide_icon,
Div(content, id=self._ids.content(side)),
resizer,
cls=panel_cls,
id=self._ids.panel(side)
cls="mf-panel-header"
)
body = Div(
header,
Div(content, id=self._ids.content(side), cls="mf-panel-content"),
cls="mf-panel-body"
)
if side == "left":
return Div(
body,
resizer,
cls=panel_cls,
id=self._ids.panel(side)
)
else:
return Div(
resizer,
body,
cls=panel_cls,
id=self._ids.panel(side)
)
else:
return Div(
resizer,
hide_icon,
Div(content, id=self._ids.content(side)),
cls=panel_cls,
id=self._ids.panel(side)
)
if side == "left":
return Div(
hide_icon,
Div(content, id=self._ids.content(side)),
resizer,
cls=panel_cls,
id=self._ids.panel(side)
)
else:
return Div(
resizer,
hide_icon,
Div(content, id=self._ids.content(side)),
cls=panel_cls,
id=self._ids.panel(side)
)
def _mk_main(self):
return Div(
@@ -195,13 +242,17 @@ class Panel(MultipleInstance):
enabled = self.conf.left if side == "left" else self.conf.right
if not enabled:
return None
show_display = self.conf.show_display_left if side == "left" else self.conf.show_display_right
if not show_display:
return None
is_visible = self._state.left_visible if side == "left" else self._state.right_visible
icon_cls = "hidden" if is_visible else f"mf-panel-show-icon mf-panel-show-icon-{side}"
return mk.icon(
more_horizontal20_regular,
command=self.commands.toggle_side(side, True),
command=self.commands.set_side_visible(side, True),
cls=icon_cls,
id=f"{self._id}_show_{side}"
)

View File

@@ -45,7 +45,8 @@ class Search(MultipleInstance):
items_names=None, # what is the name of the items to filter
items=None, # first set of items to filter
get_attr: Callable[[Any], str] = None, # items is a list of objects: how to get the str to filter
template: Callable[[Any], Any] = None): # once filtered, what to render ?
template: Callable[[Any], Any] = None, # once filtered, what to render ?
max_height: int = 400):
"""
Represents a component for managing and filtering a list of items based on specific criteria.
@@ -66,6 +67,7 @@ class Search(MultipleInstance):
self.get_attr = get_attr or (lambda x: x)
self.template = template or (lambda x: Div(self.get_attr(x)))
self.commands = Commands(self)
self.max_height = max_height
def set_items(self, items):
self.items = items
@@ -106,6 +108,7 @@ class Search(MultipleInstance):
*self._mk_search_results(),
id=f"{self._id}-results",
cls="mf-search-results",
style="max-height: 400px;" if self.max_height else None
),
id=f"{self._id}",
)