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()