Files
MyFastHtml/src/myfasthtml/controls/Panel.py

221 lines
6.2 KiB
Python

from dataclasses import dataclass
from typing import Literal
from fasthtml.components import Div
from fasthtml.xtend import Script
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance
from myfasthtml.icons.fluent_p1 import more_horizontal20_regular
from myfasthtml.icons.fluent_p2 import subtract20_regular
class PanelIds:
def __init__(self, owner):
self._owner = owner
@property
def main(self):
return f"{self._owner.get_id()}_m"
@property
def right(self):
""" Right panel's content"""
return f"{self._owner.get_id()}_cr"
@property
def left(self):
""" Left panel's content"""
return f"{self._owner.get_id()}_cl"
def panel(self, side: Literal["left", "right"]):
return f"{self._owner.get_id()}_pl" if side == "left" else f"{self._owner.get_id()}_pr"
def content(self, side: Literal["left", "right"]):
return self.left if side == "left" else self.right
@dataclass
class PanelConf:
left: bool = False
right: bool = True
class PanelState(DbObject):
def __init__(self, owner, name=None):
super().__init__(owner, name=name)
with self.initializing():
self.left_visible: bool = True
self.right_visible: bool = True
self.left_width: int = 250
self.right_width: int = 250
class Commands(BaseCommands):
def toggle_side(self, side: Literal["left", "right"], visible: bool = None):
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)}")
def update_side_width(self, side: Literal["left", "right"]):
"""
Create a command to update panel's side width.
Args:
side: Which panel's side to update ("left" or "right")
Returns:
Command: Command object for updating panel's side width
"""
return Command(f"UpdatePanelSideWidth_{side}",
f"Update {side} side panel width",
self._owner,
self._owner.update_side_width,
args=[side]).htmx(target=f"#{self._owner.get_ids().panel(side)}")
class Panel(MultipleInstance):
"""
Represents a user interface panel that supports customizable left, main, and right components.
The `Panel` class is used to create and manage a panel layout with optional left, main,
and right sections. It provides functionality to set the components of the panel, toggle
sides, and adjust the width of the sides dynamically. The class also handles rendering
the panel with appropriate HTML elements and JavaScript for interactivity.
"""
def __init__(self, parent, conf=None, _id=None):
super().__init__(parent, _id=_id)
self.conf = conf or PanelConf()
self.commands = Commands(self)
self._state = PanelState(self)
self._main = None
self._right = None
self._left = None
self._ids = PanelIds(self)
def get_ids(self):
return self._ids
def update_side_width(self, side, width):
if side == "left":
self._state.left_width = width
else:
self._state.right_width = width
return self._mk_panel(side)
def toggle_side(self, side, visible):
if side == "left":
self._state.left_visible = visible
else:
self._state.right_visible = visible
return self._mk_panel(side), self._mk_show_icon(side)
def set_main(self, main):
self._main = main
return self
def set_right(self, right):
self._right = right
return Div(self._right, id=self._ids.right)
def set_left(self, left):
self._left = left
return Div(self._left, id=self._ids.left)
def _mk_panel(self, side: Literal["left", "right"]):
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
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),
cls="mf-panel-hide-icon"
)
panel_cls = f"mf-panel-{side}"
if not visible:
panel_cls += " mf-hidden"
# Left panel: content then resizer (resizer on the right)
# Right panel: resizer then content (resizer on the left)
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(
self._mk_show_icon("left"),
Div(self._main, id=self._ids.main, cls="mf-panel-main"),
self._mk_show_icon("right"),
cls="mf-panel-main"
),
def _mk_show_icon(self, side: Literal["left", "right"]):
"""
Create show icon for a panel side if it's hidden.
Args:
side: Which panel side ("left" or "right")
Returns:
Div with icon if panel is hidden, None otherwise
"""
enabled = self.conf.left if side == "left" else self.conf.right
if not enabled:
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),
cls=icon_cls,
id=f"{self._id}_show_{side}"
)
def render(self):
return Div(
self._mk_panel("left"),
self._mk_main(),
self._mk_panel("right"),
Script(f"initResizer('{self._id}');"),
cls="mf-panel",
id=self._id,
)
def __ft__(self):
return self.render()