I can validate formatting in editor

This commit is contained in:
2026-02-01 21:49:46 +01:00
parent d7ec99c3d9
commit 0620cb678b
23 changed files with 794 additions and 501 deletions

View File

@@ -13,8 +13,9 @@ from pandas import DataFrame
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.CycleStateControl import CycleStateControl
from myfasthtml.controls.DataGridColumnsManager import DataGridColumnsManager
from myfasthtml.controls.DataGridFormattingEditor import DataGridFormattingEditor
from myfasthtml.controls.DataGridQuery import DataGridQuery, DG_QUERY_FILTER
from myfasthtml.controls.DslEditor import DslEditor
from myfasthtml.controls.DslEditor import DslEditorConf
from myfasthtml.controls.Mouse import Mouse
from myfasthtml.controls.Panel import Panel, PanelConf
from myfasthtml.controls.datagrid_objects import DataGridColumnState, DataGridRowState, \
@@ -180,7 +181,7 @@ class DataGrid(MultipleInstance):
self.init_from_dataframe(self._state.ne_df, init_state=False) # state comes from DatagridState
# add Panel
self._panel = Panel(self, conf=PanelConf(show_display_right=False), _id="-panel")
self._panel = Panel(self, conf=PanelConf(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"))
self.bind_command("ToggleFormattingEditor", self._panel.commands.toggle_side("right"))
@@ -206,7 +207,12 @@ class DataGrid(MultipleInstance):
self._columns_manager.bind_command("ToggleColumn", self.commands.on_column_changed())
self._columns_manager.bind_command("UpdateColumn", self.commands.on_column_changed())
self._formatting_editor = DslEditor(self, dsl=FormattingDSL())
editor_conf = DslEditorConf()
self._formatting_editor = DataGridFormattingEditor(self,
conf=editor_conf,
dsl=FormattingDSL(),
save_state=self._settings.save_state,
_id="#formatting_editor")
# other definitions
self._mouse_support = {

View File

@@ -0,0 +1,7 @@
from myfasthtml.controls.DslEditor import DslEditor
class DataGridFormattingEditor(DslEditor):
def on_dsl_change(self, dsl):
pass

View File

@@ -14,6 +14,11 @@ from myfasthtml.controls.helpers import mk
from myfasthtml.core.DataGridsRegistry import DataGridsRegistry
from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.dsls import DslsManager
from myfasthtml.core.formatting.dsl.completion.engine import FormattingCompletionEngine
from myfasthtml.core.formatting.dsl.completion.provider import DatagridMetadataProvider
from myfasthtml.core.formatting.dsl.definition import FormattingDSL
from myfasthtml.core.formatting.dsl.parser import DSLParser
from myfasthtml.core.formatting.presets import DEFAULT_STYLE_PRESETS, DEFAULT_FORMATTER_PRESETS
from myfasthtml.core.instances import InstancesManager, SingleInstance
from myfasthtml.icons.fluent_p1 import table_add20_regular
@@ -72,7 +77,7 @@ class Commands(BaseCommands):
key="SelectNode")
class DataGridsManager(SingleInstance):
class DataGridsManager(SingleInstance, DatagridMetadataProvider):
def __init__(self, parent, _id=None):
if not getattr(self, "_is_new_instance", False):
@@ -89,6 +94,11 @@ class DataGridsManager(SingleInstance):
# Global presets shared across all DataGrids
self.style_presets: dict = DEFAULT_STYLE_PRESETS.copy()
self.formatter_presets: dict = DEFAULT_FORMATTER_PRESETS.copy()
# register the auto-completion for the formatter DSL
DslsManager.register(FormattingDSL().get_id(),
FormattingCompletionEngine(self),
DSLParser())
def upload_from_source(self):
file_upload = FileUpload(self)
@@ -104,6 +114,7 @@ class DataGridsManager(SingleInstance):
dg_conf = DatagridConf(namespace=namespace, name=name)
dg = DataGrid(self._tabs_manager, conf=dg_conf, save_state=True) # first time the Datagrid is created
dg.init_from_dataframe(df)
self._registry.put(namespace, name, dg.get_id())
document = DocumentDefinition(
document_id=str(uuid.uuid4()),
namespace=namespace,
@@ -154,6 +165,26 @@ class DataGridsManager(SingleInstance):
self._tree.clear()
return self._tree
# === DatagridMetadataProvider ===
def list_tables(self):
return self._registry.get_all_tables()
def list_columns(self, table_name):
return self._registry.get_columns(table_name)
def list_column_values(self, table_name, column_name):
return self._registry.get_column_values(table_name, column_name)
def get_row_count(self, table_name):
return self._registry.get_row_count(table_name)
def list_style_presets(self) -> list[str]:
return list(self.style_presets.keys())
def list_format_presets(self) -> list[str]:
return list(self.formatter_presets.keys())
# === Presets Management ===
def get_style_presets(self) -> dict:
@@ -194,6 +225,8 @@ class DataGridsManager(SingleInstance):
if name in self.formatter_presets:
del self.formatter_presets[name]
# === UI ===
def mk_main_icons(self):
return Div(
mk.icon(folder_open20_regular, tooltip="Upload from source", command=self.commands.upload_from_source()),

View File

@@ -16,6 +16,7 @@ from fasthtml.components import *
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.dsl.base import DSLDefinition
from myfasthtml.core.instances import MultipleInstance
@@ -25,19 +26,22 @@ logger = logging.getLogger("DslEditor")
@dataclass
class DslEditorConf:
"""Configuration for DslEditor."""
name: str = None
line_numbers: bool = True
autocompletion: bool = True
linting: bool = True
placeholder: str = ""
readonly: bool = False
class DslEditorState:
class DslEditorState(DbObject):
"""Non-persisted state for DslEditor."""
def __init__(self):
self.content: str = ""
self.auto_save: bool = True
def __init__(self, owner, name, save_state):
with self.initializing():
super().__init__(owner, name=name, save_state=save_state)
self.content: str = ""
self.auto_save: bool = True
class Commands(BaseCommands):
@@ -87,13 +91,14 @@ class DslEditor(MultipleInstance):
parent,
dsl: DSLDefinition,
conf: Optional[DslEditorConf] = None,
save_state: bool = True,
_id: Optional[str] = None,
):
super().__init__(parent, _id=_id)
self._dsl = dsl
self.conf = conf or DslEditorConf()
self._state = DslEditorState()
self._state = DslEditorState(self, name=conf.name, save_state=save_state)
self.commands = Commands(self)
logger.debug(f"DslEditor created with id={self._id}, dsl={dsl.name}")
@@ -114,9 +119,9 @@ class DslEditor(MultipleInstance):
self.on_content_changed()
logger.debug(f"Content updated: {len(content)} chars")
def toggle_auto_save(self) -> None:
def toggle_auto_save(self):
self._state.auto_save = not self._state.auto_save
self._mk_auto_save()
return self._mk_auto_save()
def on_content_changed(self) -> None:
pass
@@ -128,9 +133,11 @@ class DslEditor(MultipleInstance):
"textareaId": f"ta_{self._id}",
"lineNumbers": self.conf.line_numbers,
"autocompletion": self.conf.autocompletion,
"linting": self.conf.linting,
"placeholder": self.conf.placeholder,
"readonly": self.conf.readonly,
"updateCommandId": str(self.commands.update_content().id),
"dslId": self._dsl.get_id(),
"dsl": {
"name": self._dsl.name,
"completions": self._dsl.completions,

View File

@@ -159,31 +159,31 @@ 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.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 show_title:
@@ -202,6 +202,7 @@ class Panel(MultipleInstance):
body,
resizer,
cls=panel_cls,
style=f"width: {self._state.left_width}px;",
id=self._ids.panel(side)
)
else:
@@ -209,6 +210,7 @@ class Panel(MultipleInstance):
resizer,
body,
cls=panel_cls,
style=f"width: {self._state.right_width}px;",
id=self._ids.panel(side)
)
else:
@@ -218,6 +220,7 @@ class Panel(MultipleInstance):
Div(content, id=self._ids.content(side)),
resizer,
cls=panel_cls,
style=f"width: {self._state.left_width}px;",
id=self._ids.panel(side)
)
else:
@@ -226,6 +229,7 @@ class Panel(MultipleInstance):
hide_icon,
Div(content, id=self._ids.content(side)),
cls=panel_cls,
style=f"width: {self._state.left_width}px;",
id=self._ids.panel(side)
)
@@ -250,14 +254,14 @@ 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.set_side_visible(side, True),