Fixed FormattingRules not being applied
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -220,7 +220,10 @@ class DataGrid(MultipleInstance):
|
|||||||
name, namespace = (conf.name, conf.namespace) if conf else ("No name", "__default__")
|
name, namespace = (conf.name, conf.namespace) if conf else ("No name", "__default__")
|
||||||
self._settings = DatagridSettings(self, save_state=save_state, name=name, namespace=namespace)
|
self._settings = DatagridSettings(self, save_state=save_state, name=name, namespace=namespace)
|
||||||
self._state = DatagridState(self, save_state=self._settings.save_state)
|
self._state = DatagridState(self, save_state=self._settings.save_state)
|
||||||
self._formatting_engine = FormattingEngine()
|
self._formatting_provider = DatagridMetadataProvider(self._parent)
|
||||||
|
self._formatting_engine = FormattingEngine(
|
||||||
|
rule_presets_provider=lambda: self._formatting_provider.rule_presets
|
||||||
|
)
|
||||||
self._columns = None
|
self._columns = None
|
||||||
self.commands = Commands(self)
|
self.commands = Commands(self)
|
||||||
|
|
||||||
@@ -268,8 +271,7 @@ class DataGrid(MultipleInstance):
|
|||||||
# self._columns_manager.bind_command("SaveColumnDetails", self.commands.on_column_changed())
|
# self._columns_manager.bind_command("SaveColumnDetails", self.commands.on_column_changed())
|
||||||
|
|
||||||
if self._settings.enable_formatting:
|
if self._settings.enable_formatting:
|
||||||
provider = DatagridMetadataProvider(self._parent)
|
completion_engine = FormattingCompletionEngine(self._formatting_provider, self.get_table_name())
|
||||||
completion_engine = FormattingCompletionEngine(provider, self.get_table_name())
|
|
||||||
editor_conf = DslEditorConf(engine_id=completion_engine.get_id())
|
editor_conf = DslEditorConf(engine_id=completion_engine.get_id())
|
||||||
dsl = FormattingDSL()
|
dsl = FormattingDSL()
|
||||||
self._formatting_editor = DataGridFormattingEditor(self,
|
self._formatting_editor = DataGridFormattingEditor(self,
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
|||||||
self._query.bind_command("CancelQuery", self.commands.apply_filter())
|
self._query.bind_command("CancelQuery", self.commands.apply_filter())
|
||||||
|
|
||||||
# Add Menu
|
# Add Menu
|
||||||
self._menu = Menu(self, conf=MenuConf(["ResetView"]))
|
self._menu = Menu(self, conf=MenuConf(["ResetView"]), _id="-menu")
|
||||||
|
|
||||||
logger.debug(f"HierarchicalCanvasGraph created with id={self._id}, "
|
logger.debug(f"HierarchicalCanvasGraph created with id={self._id}, "
|
||||||
f"nodes={len(conf.nodes)}, edges={len(conf.edges)}")
|
f"nodes={len(conf.nodes)}, edges={len(conf.edges)}")
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ from myfasthtml.core.formatting.dataclasses import RulePreset
|
|||||||
from myfasthtml.core.formatting.presets import (
|
from myfasthtml.core.formatting.presets import (
|
||||||
DEFAULT_FORMATTER_PRESETS, DEFAULT_STYLE_PRESETS, DEFAULT_RULE_PRESETS,
|
DEFAULT_FORMATTER_PRESETS, DEFAULT_STYLE_PRESETS, DEFAULT_RULE_PRESETS,
|
||||||
)
|
)
|
||||||
from myfasthtml.core.instances import SingleInstance, InstancesManager
|
from myfasthtml.core.instances import UniqueInstance, InstancesManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DatagridMetadataProvider(SingleInstance, BaseMetadataProvider):
|
class DatagridMetadataProvider(UniqueInstance, BaseMetadataProvider):
|
||||||
"""Concrete session-scoped metadata provider for DataGrid DSL engines.
|
"""Concrete session-scoped metadata provider for DataGrid DSL engines.
|
||||||
|
|
||||||
Implements BaseMetadataProvider by delegating live data queries to
|
Implements BaseMetadataProvider by delegating live data queries to
|
||||||
@@ -36,8 +36,7 @@ class DatagridMetadataProvider(SingleInstance, BaseMetadataProvider):
|
|||||||
all_tables_formats: Global format rules applied to all tables.
|
all_tables_formats: Global format rules applied to all tables.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None, session: Optional[dict] = None,
|
def __init__(self, parent=None, session: Optional[dict] = None, _id: Optional[str] = None):
|
||||||
_id: Optional[str] = None):
|
|
||||||
super().__init__(parent, session, _id)
|
super().__init__(parent, session, _id)
|
||||||
self.style_presets: dict = DEFAULT_STYLE_PRESETS.copy()
|
self.style_presets: dict = DEFAULT_STYLE_PRESETS.copy()
|
||||||
self.formatter_presets: dict = DEFAULT_FORMATTER_PRESETS.copy()
|
self.formatter_presets: dict = DEFAULT_FORMATTER_PRESETS.copy()
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ class FormattingEngine:
|
|||||||
style_presets: dict = None,
|
style_presets: dict = None,
|
||||||
formatter_presets: dict = None,
|
formatter_presets: dict = None,
|
||||||
rule_presets: dict = None,
|
rule_presets: dict = None,
|
||||||
lookup_resolver: Callable[[str, str, str], dict] = None
|
lookup_resolver: Callable[[str, str, str], dict] = None,
|
||||||
|
rule_presets_provider: Callable[[], dict] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize the FormattingEngine.
|
Initialize the FormattingEngine.
|
||||||
@@ -41,11 +42,20 @@ class FormattingEngine:
|
|||||||
formatter_presets: Custom formatter presets. If None, uses defaults.
|
formatter_presets: Custom formatter presets. If None, uses defaults.
|
||||||
rule_presets: Named rule presets (list of FormatRule dicts). If None, uses defaults.
|
rule_presets: Named rule presets (list of FormatRule dicts). If None, uses defaults.
|
||||||
lookup_resolver: Function for resolving enum datagrid sources.
|
lookup_resolver: Function for resolving enum datagrid sources.
|
||||||
|
rule_presets_provider: Callable returning the current rule_presets dict.
|
||||||
|
When provided, takes precedence over rule_presets on every apply_format call.
|
||||||
|
Use this to keep the engine in sync with a shared provider.
|
||||||
"""
|
"""
|
||||||
self._condition_evaluator = ConditionEvaluator()
|
self._condition_evaluator = ConditionEvaluator()
|
||||||
self._style_resolver = StyleResolver(style_presets)
|
self._style_resolver = StyleResolver(style_presets)
|
||||||
self._formatter_resolver = FormatterResolver(formatter_presets, lookup_resolver)
|
self._formatter_resolver = FormatterResolver(formatter_presets, lookup_resolver)
|
||||||
self._rule_presets = rule_presets if rule_presets is not None else DEFAULT_RULE_PRESETS
|
self._rule_presets = rule_presets if rule_presets is not None else DEFAULT_RULE_PRESETS
|
||||||
|
self._rule_presets_provider = rule_presets_provider
|
||||||
|
|
||||||
|
def _get_rule_presets(self) -> dict:
|
||||||
|
if self._rule_presets_provider is not None:
|
||||||
|
return self._rule_presets_provider()
|
||||||
|
return self._rule_presets
|
||||||
|
|
||||||
def apply_format(
|
def apply_format(
|
||||||
self,
|
self,
|
||||||
@@ -99,8 +109,8 @@ class FormattingEngine:
|
|||||||
"""
|
"""
|
||||||
Replace any FormatRule that references a rule preset with the preset's rules.
|
Replace any FormatRule that references a rule preset with the preset's rules.
|
||||||
|
|
||||||
A rule is a rule preset reference when its formatter has a preset name
|
A rule is a rule preset reference when its formatter or style has a preset name
|
||||||
that exists in rule_presets (and not in formatter_presets).
|
that exists in rule_presets.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
rules: Original list of FormatRule
|
rules: Original list of FormatRule
|
||||||
@@ -108,22 +118,26 @@ class FormattingEngine:
|
|||||||
Returns:
|
Returns:
|
||||||
Expanded list with preset references replaced by their FormatRules
|
Expanded list with preset references replaced by their FormatRules
|
||||||
"""
|
"""
|
||||||
|
rule_presets = self._get_rule_presets()
|
||||||
expanded = []
|
expanded = []
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
preset_name = self._get_rule_preset_name(rule)
|
preset_name = self._get_rule_preset_name(rule, rule_presets)
|
||||||
if preset_name:
|
if preset_name:
|
||||||
expanded.extend(self._rule_presets[preset_name].rules)
|
expanded.extend(rule_presets[preset_name].rules)
|
||||||
else:
|
else:
|
||||||
expanded.append(rule)
|
expanded.append(rule)
|
||||||
return expanded
|
return expanded
|
||||||
|
|
||||||
def _get_rule_preset_name(self, rule: FormatRule) -> str | None:
|
def _get_rule_preset_name(self, rule: FormatRule, rule_presets: dict) -> str | None:
|
||||||
"""Return the preset name if the rule's formatter references a rule preset, else None."""
|
"""Return the preset name if the rule references a rule preset via format() or style(), else None."""
|
||||||
if rule.formatter is None:
|
if rule.formatter is not None:
|
||||||
return None
|
preset = getattr(rule.formatter, "preset", None)
|
||||||
preset = getattr(rule.formatter, "preset", None)
|
if preset and preset in rule_presets:
|
||||||
if preset and preset in self._rule_presets:
|
return preset
|
||||||
return preset
|
if rule.style is not None:
|
||||||
|
preset = getattr(rule.style, "preset", None)
|
||||||
|
if preset and preset in rule_presets:
|
||||||
|
return preset
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_matching_rules(
|
def _get_matching_rules(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Optional, Literal
|
|||||||
from myfasthtml.controls.helpers import Ids
|
from myfasthtml.controls.helpers import Ids
|
||||||
from myfasthtml.core.commands import BoundCommand, Command
|
from myfasthtml.core.commands import BoundCommand, Command
|
||||||
from myfasthtml.core.constants import NO_DEFAULT_VALUE
|
from myfasthtml.core.constants import NO_DEFAULT_VALUE
|
||||||
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal
|
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal, debug_session
|
||||||
|
|
||||||
VERBOSE_VERBOSE = False
|
VERBOSE_VERBOSE = False
|
||||||
|
|
||||||
@@ -36,7 +36,13 @@ class BaseInstance:
|
|||||||
session = args[1] if len(args) > 1 and isinstance(args[1], dict) else kwargs.get("session", None)
|
session = args[1] if len(args) > 1 and isinstance(args[1], dict) else kwargs.get("session", None)
|
||||||
_id = args[2] if len(args) > 2 and isinstance(args[2], str) else kwargs.get("_id", None)
|
_id = args[2] if len(args) > 2 and isinstance(args[2], str) else kwargs.get("_id", None)
|
||||||
if VERBOSE_VERBOSE:
|
if VERBOSE_VERBOSE:
|
||||||
logger.debug(f" parent={parent}, session={session}, _id={_id}")
|
logger.debug(f" parent={parent}, session={debug_session(session)}, _id={_id}")
|
||||||
|
|
||||||
|
# for UniqueInstance, the parent is always the ultimate root parent
|
||||||
|
if issubclass(cls, UniqueInstance):
|
||||||
|
parent = BaseInstance.get_ultimate_root_parent(parent)
|
||||||
|
if VERBOSE_VERBOSE:
|
||||||
|
logger.debug(f" UniqueInstance detected. parent is set to ultimate root {parent=}")
|
||||||
|
|
||||||
# Compute _id
|
# Compute _id
|
||||||
_id = cls.compute_id(_id, parent)
|
_id = cls.compute_id(_id, parent)
|
||||||
@@ -163,7 +169,7 @@ class BaseInstance:
|
|||||||
def compute_id(cls, _id: Optional[str], parent: Optional['BaseInstance']):
|
def compute_id(cls, _id: Optional[str], parent: Optional['BaseInstance']):
|
||||||
if _id is None:
|
if _id is None:
|
||||||
prefix = cls.compute_prefix()
|
prefix = cls.compute_prefix()
|
||||||
if issubclass(cls, SingleInstance):
|
if issubclass(cls, (SingleInstance, UniqueInstance)):
|
||||||
_id = prefix
|
_id = prefix
|
||||||
else:
|
else:
|
||||||
_id = f"{prefix}-{str(uuid.uuid4())}"
|
_id = f"{prefix}-{str(uuid.uuid4())}"
|
||||||
@@ -173,6 +179,17 @@ class BaseInstance:
|
|||||||
return f"{parent.get_id()}{_id}"
|
return f"{parent.get_id()}{_id}"
|
||||||
|
|
||||||
return _id
|
return _id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_ultimate_root_parent(instance):
|
||||||
|
if instance is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
parent = instance
|
||||||
|
while True:
|
||||||
|
if parent.get_parent() is None:
|
||||||
|
return parent
|
||||||
|
parent = parent.get_parent()
|
||||||
|
|
||||||
|
|
||||||
class SingleInstance(BaseInstance):
|
class SingleInstance(BaseInstance):
|
||||||
@@ -200,7 +217,7 @@ class UniqueInstance(BaseInstance):
|
|||||||
_id: Optional[str] = None,
|
_id: Optional[str] = None,
|
||||||
auto_register: bool = True,
|
auto_register: bool = True,
|
||||||
on_init=None):
|
on_init=None):
|
||||||
super().__init__(parent, session, _id, auto_register)
|
super().__init__(BaseInstance.get_ultimate_root_parent(parent), session, _id, auto_register)
|
||||||
if on_init is not None:
|
if on_init is not None:
|
||||||
on_init()
|
on_init()
|
||||||
|
|
||||||
|
|||||||
@@ -350,3 +350,32 @@ class TestPresets:
|
|||||||
|
|
||||||
assert "background-color: purple" in css.css
|
assert "background-color: purple" in css.css
|
||||||
assert "color: yellow" in css.css
|
assert "color: yellow" in css.css
|
||||||
|
|
||||||
|
def test_i_can_expand_rule_preset_via_style(self):
|
||||||
|
"""A rule preset referenced via style() must be expanded like via format().
|
||||||
|
|
||||||
|
Why: style("traffic_light") should expand the traffic_light rule preset
|
||||||
|
(which has conditional style rules) instead of looking up "traffic_light"
|
||||||
|
as a style preset name (where it doesn't exist).
|
||||||
|
"""
|
||||||
|
engine = FormattingEngine()
|
||||||
|
rules = [FormatRule(style=Style(preset="traffic_light"))]
|
||||||
|
|
||||||
|
css, _ = engine.apply_format(rules, cell_value=-5)
|
||||||
|
|
||||||
|
assert css is not None
|
||||||
|
assert css.cls == "mf-formatting-error"
|
||||||
|
|
||||||
|
def test_i_can_expand_rule_preset_via_style_with_no_match(self):
|
||||||
|
"""A rule preset via style() with a non-matching condition returns no style.
|
||||||
|
|
||||||
|
Why: traffic_light has style("error") only if value < 0.
|
||||||
|
A positive value should produce no style output.
|
||||||
|
"""
|
||||||
|
engine = FormattingEngine()
|
||||||
|
rules = [FormatRule(style=Style(preset="traffic_light"))]
|
||||||
|
|
||||||
|
css, _ = engine.apply_format(rules, cell_value=10)
|
||||||
|
|
||||||
|
assert css is not None
|
||||||
|
assert css.cls == "mf-formatting-success"
|
||||||
|
|||||||
Reference in New Issue
Block a user