|
|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
import logging
|
|
|
|
|
from typing import Optional
|
|
|
|
|
from typing import Optional, Literal
|
|
|
|
|
|
|
|
|
|
from fasthtml.common import Form, Fieldset, Label, Input, Span
|
|
|
|
|
from fasthtml.components import Div
|
|
|
|
|
@@ -14,378 +14,406 @@ from myfasthtml.controls.helpers import mk
|
|
|
|
|
from myfasthtml.core.commands import Command
|
|
|
|
|
from myfasthtml.core.dbmanager import DbObject
|
|
|
|
|
from myfasthtml.core.formatting.dataclasses import RulePreset
|
|
|
|
|
from myfasthtml.core.dsls import DslsManager
|
|
|
|
|
from myfasthtml.core.formatting.dsl import parse_dsl
|
|
|
|
|
from myfasthtml.core.formatting.dsl.completion.FormattingCompletionEngine 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_RULE_PRESETS
|
|
|
|
|
from myfasthtml.core.instances import SingleInstance
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger("DataGridFormattingManager")
|
|
|
|
|
|
|
|
|
|
_DSL_PLACEHOLDER = 'tables:\n style("error") if value < 0\n style("success") if value > 0'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataGridFormattingManagerState(DbObject):
|
|
|
|
|
def __init__(self, owner):
|
|
|
|
|
with self.initializing():
|
|
|
|
|
super().__init__(owner)
|
|
|
|
|
self.presets: list = []
|
|
|
|
|
self.selected_name: Optional[str] = None
|
|
|
|
|
self.ns_mode: str = "view"
|
|
|
|
|
def __init__(self, owner):
|
|
|
|
|
with self.initializing():
|
|
|
|
|
super().__init__(owner)
|
|
|
|
|
self.presets: list = []
|
|
|
|
|
self.selected_name: Optional[str] = None
|
|
|
|
|
self.ns_mode: str = "view"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Commands(BaseCommands):
|
|
|
|
|
def new_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"NewPreset",
|
|
|
|
|
"New preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.on_new_preset,
|
|
|
|
|
icon=IconsHelper.get("Add20Regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
def new_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"NewPreset",
|
|
|
|
|
"New preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_new_preset,
|
|
|
|
|
icon=IconsHelper.get("add_circle20_regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
def save_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"SavePreset",
|
|
|
|
|
"Save preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.on_save_preset,
|
|
|
|
|
icon=IconsHelper.get("Save20Regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
def save_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"SavePreset",
|
|
|
|
|
"Save preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_save_preset,
|
|
|
|
|
icon=IconsHelper.get("Save20Regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
def rename_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"RenamePreset",
|
|
|
|
|
"Rename preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.on_rename_preset,
|
|
|
|
|
icon=IconsHelper.get("Rename20Regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
def rename_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"RenamePreset",
|
|
|
|
|
"Rename preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_rename_preset,
|
|
|
|
|
icon=IconsHelper.get("edit20_regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
def delete_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"DeletePreset",
|
|
|
|
|
"Delete preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.on_delete_preset,
|
|
|
|
|
icon=IconsHelper.get("Delete20Regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
def delete_preset(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"DeletePreset",
|
|
|
|
|
"Delete preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_delete_preset,
|
|
|
|
|
icon=IconsHelper.get("Delete20Regular"),
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
def confirm_new(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"ConfirmNew",
|
|
|
|
|
"Confirm new preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.on_confirm_new,
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
def confirm_new(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"ConfirmNew",
|
|
|
|
|
"Confirm new preset",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_confirm_new,
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
def confirm_rename(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"ConfirmRename",
|
|
|
|
|
"Confirm rename",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.on_confirm_rename,
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
def confirm_rename(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"ConfirmRename",
|
|
|
|
|
"Confirm rename",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_confirm_rename,
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
def cancel(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"Cancel",
|
|
|
|
|
"Cancel",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.on_cancel,
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
def cancel(self):
|
|
|
|
|
return Command(
|
|
|
|
|
"Cancel",
|
|
|
|
|
"Cancel",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_cancel,
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
def select_preset(self, name: str):
|
|
|
|
|
return Command(
|
|
|
|
|
"SelectPreset",
|
|
|
|
|
f"Select {name}",
|
|
|
|
|
self._owner,
|
|
|
|
|
self._owner.handle_select_preset,
|
|
|
|
|
args=[name],
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DataGridFormattingManager(SingleInstance):
|
|
|
|
|
def __init__(self, parent, _id=None):
|
|
|
|
|
super().__init__(parent, _id=_id)
|
|
|
|
|
self._state = DataGridFormattingManagerState(self)
|
|
|
|
|
self.commands = Commands(self)
|
|
|
|
|
def __init__(self, parent, _id=None):
|
|
|
|
|
super().__init__(parent, _id=_id)
|
|
|
|
|
self._state = DataGridFormattingManagerState(self)
|
|
|
|
|
self.commands = Commands(self)
|
|
|
|
|
|
|
|
|
|
self._panel = Panel(
|
|
|
|
|
self,
|
|
|
|
|
conf=PanelConf(left=True, right=False, left_title="Presets"),
|
|
|
|
|
_id="-panel",
|
|
|
|
|
)
|
|
|
|
|
self._menu = Menu(
|
|
|
|
|
self,
|
|
|
|
|
conf=MenuConf(fixed_items=["NewPreset", "SavePreset", "RenamePreset", "DeletePreset"]),
|
|
|
|
|
save_state=False,
|
|
|
|
|
_id="-menu",
|
|
|
|
|
)
|
|
|
|
|
self._search = Search(
|
|
|
|
|
self,
|
|
|
|
|
items_names="Presets",
|
|
|
|
|
template=self._mk_preset_item,
|
|
|
|
|
_id="-search",
|
|
|
|
|
)
|
|
|
|
|
self._editor = DslEditor(
|
|
|
|
|
self,
|
|
|
|
|
dsl=FormattingDSL(),
|
|
|
|
|
conf=DslEditorConf(
|
|
|
|
|
save_button=False,
|
|
|
|
|
autocompletion=False,
|
|
|
|
|
linting=False,
|
|
|
|
|
placeholder=_DSL_PLACEHOLDER,
|
|
|
|
|
),
|
|
|
|
|
save_state=False,
|
|
|
|
|
_id="-editor",
|
|
|
|
|
)
|
|
|
|
|
self._panel = Panel(
|
|
|
|
|
self,
|
|
|
|
|
conf=PanelConf(left=True, right=False, left_title="Presets"),
|
|
|
|
|
_id="-panel",
|
|
|
|
|
)
|
|
|
|
|
self._menu = Menu(
|
|
|
|
|
self,
|
|
|
|
|
conf=MenuConf(fixed_items=["NewPreset", "SavePreset", "RenamePreset", "DeletePreset"]),
|
|
|
|
|
save_state=False,
|
|
|
|
|
_id="-menu",
|
|
|
|
|
)
|
|
|
|
|
self._search = Search(
|
|
|
|
|
self,
|
|
|
|
|
items_names="Presets",
|
|
|
|
|
template=self._mk_preset_item,
|
|
|
|
|
_id="-search",
|
|
|
|
|
)
|
|
|
|
|
provider = DatagridMetadataProvider(self)
|
|
|
|
|
completion_engine = FormattingCompletionEngine(provider, "")
|
|
|
|
|
DslsManager.register(completion_engine, DSLParser())
|
|
|
|
|
|
|
|
|
|
logger.debug(f"DataGridFormattingManager created with id={self._id}")
|
|
|
|
|
self._editor = DslEditor(
|
|
|
|
|
self,
|
|
|
|
|
dsl=FormattingDSL(),
|
|
|
|
|
conf=DslEditorConf(
|
|
|
|
|
save_button=False,
|
|
|
|
|
autocompletion=True,
|
|
|
|
|
linting=True,
|
|
|
|
|
engine_id=completion_engine.get_id(),
|
|
|
|
|
),
|
|
|
|
|
save_state=False,
|
|
|
|
|
_id="-editor",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# === Helpers ===
|
|
|
|
|
self.handle_select_preset(self._state.selected_name)
|
|
|
|
|
self._sync_provider()
|
|
|
|
|
logger.debug(f"DataGridFormattingManager created with id={self._id}")
|
|
|
|
|
|
|
|
|
|
def _get_all_presets(self) -> list:
|
|
|
|
|
"""Returns builtin presets followed by user presets."""
|
|
|
|
|
return list(DEFAULT_RULE_PRESETS.values()) + self._state.presets
|
|
|
|
|
def get_main_content_id(self):
|
|
|
|
|
return self._panel.get_ids().main
|
|
|
|
|
|
|
|
|
|
def _is_builtin(self, name: str) -> bool:
|
|
|
|
|
return name in DEFAULT_RULE_PRESETS
|
|
|
|
|
# === Helpers ===
|
|
|
|
|
|
|
|
|
|
def _get_selected_preset(self) -> Optional[RulePreset]:
|
|
|
|
|
if not self._state.selected_name:
|
|
|
|
|
return None
|
|
|
|
|
for p in self._get_all_presets():
|
|
|
|
|
if p.name == self._state.selected_name:
|
|
|
|
|
return p
|
|
|
|
|
return None
|
|
|
|
|
def _get_all_presets(self) -> list:
|
|
|
|
|
"""Returns builtin presets followed by user presets."""
|
|
|
|
|
return list(DEFAULT_RULE_PRESETS.values()) + self._state.presets
|
|
|
|
|
|
|
|
|
|
def _get_user_preset(self, name: str) -> Optional[RulePreset]:
|
|
|
|
|
for p in self._state.presets:
|
|
|
|
|
if p.name == name:
|
|
|
|
|
return p
|
|
|
|
|
return None
|
|
|
|
|
def _is_builtin(self, name: str) -> bool:
|
|
|
|
|
return name in DEFAULT_RULE_PRESETS
|
|
|
|
|
|
|
|
|
|
def _parse_dsl_to_rules(self, dsl_text: str) -> list:
|
|
|
|
|
"""Parse DSL text and extract FormatRule objects, ignoring scopes."""
|
|
|
|
|
try:
|
|
|
|
|
scoped_rules = parse_dsl(dsl_text)
|
|
|
|
|
return [sr.rule for sr in scoped_rules]
|
|
|
|
|
except Exception:
|
|
|
|
|
return []
|
|
|
|
|
def _get_selected_preset(self) -> Optional[RulePreset]:
|
|
|
|
|
if not self._state.selected_name:
|
|
|
|
|
return None
|
|
|
|
|
for p in self._get_all_presets():
|
|
|
|
|
if p.name == self._state.selected_name:
|
|
|
|
|
return p
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# === Command handlers ===
|
|
|
|
|
def _get_user_preset(self, name: str) -> Optional[RulePreset]:
|
|
|
|
|
for p in self._state.presets:
|
|
|
|
|
if p.name == name:
|
|
|
|
|
return p
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def on_new_preset(self):
|
|
|
|
|
self._state.ns_mode = "new"
|
|
|
|
|
return self.render()
|
|
|
|
|
def _parse_dsl_to_rules(self, dsl_text: str) -> list:
|
|
|
|
|
"""Parse DSL text and extract FormatRule objects, ignoring scopes."""
|
|
|
|
|
try:
|
|
|
|
|
scoped_rules = parse_dsl(dsl_text)
|
|
|
|
|
return [sr.rule for sr in scoped_rules]
|
|
|
|
|
except Exception:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
def on_save_preset(self):
|
|
|
|
|
if not self._state.selected_name or self._is_builtin(self._state.selected_name):
|
|
|
|
|
return self.render()
|
|
|
|
|
preset = self._get_user_preset(self._state.selected_name)
|
|
|
|
|
if preset is None:
|
|
|
|
|
return self.render()
|
|
|
|
|
dsl = self._editor.get_content()
|
|
|
|
|
preset.dsl = dsl
|
|
|
|
|
preset.rules = self._parse_dsl_to_rules(dsl)
|
|
|
|
|
logger.debug(f"Saved preset '{preset.name}' with {len(preset.rules)} rules")
|
|
|
|
|
return self.render()
|
|
|
|
|
def _sync_provider(self):
|
|
|
|
|
"""Sync all presets (builtin + user) into the session-scoped metadata provider."""
|
|
|
|
|
provider = DatagridMetadataProvider(self)
|
|
|
|
|
provider.rule_presets = {p.name: p for p in self._get_all_presets()}
|
|
|
|
|
|
|
|
|
|
def on_rename_preset(self):
|
|
|
|
|
if not self._state.selected_name or self._is_builtin(self._state.selected_name):
|
|
|
|
|
return self.render()
|
|
|
|
|
self._state.ns_mode = "rename"
|
|
|
|
|
return self.render()
|
|
|
|
|
# === Command handlers ===
|
|
|
|
|
|
|
|
|
|
def on_delete_preset(self):
|
|
|
|
|
if not self._state.selected_name or self._is_builtin(self._state.selected_name):
|
|
|
|
|
return self.render()
|
|
|
|
|
self._state.presets = [p for p in self._state.presets if p.name != self._state.selected_name]
|
|
|
|
|
self._state.selected_name = None
|
|
|
|
|
self._editor.set_content("")
|
|
|
|
|
self._editor.conf.readonly = False
|
|
|
|
|
logger.debug(f"Deleted preset '{self._state.selected_name}'")
|
|
|
|
|
return self.render()
|
|
|
|
|
def handle_new_preset(self):
|
|
|
|
|
self._state.ns_mode = "new"
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
def on_select_preset(self, name: str):
|
|
|
|
|
preset = None
|
|
|
|
|
for p in self._get_all_presets():
|
|
|
|
|
if p.name == name:
|
|
|
|
|
preset = p
|
|
|
|
|
break
|
|
|
|
|
if preset is None:
|
|
|
|
|
return self.render()
|
|
|
|
|
self._state.selected_name = name
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
self._editor.set_content(preset.dsl)
|
|
|
|
|
self._editor.conf.readonly = self._is_builtin(name)
|
|
|
|
|
logger.debug(f"Selected preset '{name}', readonly={self._editor.conf.readonly}")
|
|
|
|
|
return self.render()
|
|
|
|
|
def handle_save_preset(self):
|
|
|
|
|
if not self._state.selected_name or self._is_builtin(self._state.selected_name):
|
|
|
|
|
return self.render()
|
|
|
|
|
preset = self._get_user_preset(self._state.selected_name)
|
|
|
|
|
if preset is None:
|
|
|
|
|
return self.render()
|
|
|
|
|
dsl = self._editor.get_content()
|
|
|
|
|
preset.dsl = dsl
|
|
|
|
|
preset.rules = self._parse_dsl_to_rules(dsl)
|
|
|
|
|
self._state.save()
|
|
|
|
|
self._sync_provider()
|
|
|
|
|
logger.debug(f"Saved preset '{preset.name}' with {len(preset.rules)} rules")
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
def on_confirm_new(self, client_response):
|
|
|
|
|
name = (client_response.get("name") or "").strip()
|
|
|
|
|
description = (client_response.get("description") or "").strip()
|
|
|
|
|
def handle_rename_preset(self):
|
|
|
|
|
if not self._state.selected_name or self._is_builtin(self._state.selected_name):
|
|
|
|
|
return self.render()
|
|
|
|
|
self._state.ns_mode = "rename"
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
if not name:
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
def handle_delete_preset(self):
|
|
|
|
|
if not self._state.selected_name or self._is_builtin(self._state.selected_name):
|
|
|
|
|
return self.render()
|
|
|
|
|
deleted_name = self._state.selected_name
|
|
|
|
|
self._state.presets = [p for p in self._state.presets if p.name != deleted_name]
|
|
|
|
|
self._state.selected_name = None
|
|
|
|
|
self._editor.set_content("")
|
|
|
|
|
self._editor.conf.readonly = False
|
|
|
|
|
self._sync_provider()
|
|
|
|
|
logger.debug(f"Deleted preset '{deleted_name}'")
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
all_names = {p.name for p in self._get_all_presets()}
|
|
|
|
|
if name in all_names:
|
|
|
|
|
logger.debug(f"Cannot create preset '{name}': name already exists")
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
def handle_select_preset(self, name: str):
|
|
|
|
|
preset = None
|
|
|
|
|
for p in self._get_all_presets():
|
|
|
|
|
if p.name == name:
|
|
|
|
|
preset = p
|
|
|
|
|
break
|
|
|
|
|
if preset is None:
|
|
|
|
|
return None
|
|
|
|
|
self._state.selected_name = name
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
self._editor.set_content(preset.dsl)
|
|
|
|
|
self._editor.conf.readonly = self._is_builtin(name)
|
|
|
|
|
logger.debug(f"Selected preset '{name}', readonly={self._editor.conf.readonly}")
|
|
|
|
|
return self.render() # to also update the selected item in the search
|
|
|
|
|
|
|
|
|
|
new_preset = RulePreset(name=name, description=description, rules=[], dsl="")
|
|
|
|
|
self._state.presets.append(new_preset)
|
|
|
|
|
self._state.selected_name = name
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
self._editor.set_content("")
|
|
|
|
|
self._editor.conf.readonly = False
|
|
|
|
|
logger.debug(f"Created preset '{name}'")
|
|
|
|
|
return self.render()
|
|
|
|
|
def handle_confirm_new(self, client_response):
|
|
|
|
|
name = (client_response.get("name") or "").strip()
|
|
|
|
|
description = (client_response.get("description") or "").strip()
|
|
|
|
|
|
|
|
|
|
def on_confirm_rename(self, client_response):
|
|
|
|
|
name = (client_response.get("name") or "").strip()
|
|
|
|
|
description = (client_response.get("description") or "").strip()
|
|
|
|
|
if not name:
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
preset = self._get_user_preset(self._state.selected_name)
|
|
|
|
|
if not name or preset is None:
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
all_names = {p.name for p in self._get_all_presets()}
|
|
|
|
|
if name in all_names:
|
|
|
|
|
logger.debug(f"Cannot create preset '{name}': name already exists")
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
if name != preset.name:
|
|
|
|
|
all_names = {p.name for p in self._get_all_presets()}
|
|
|
|
|
if name in all_names:
|
|
|
|
|
logger.debug(f"Cannot rename to '{name}': name already exists")
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
new_preset = RulePreset(name=name, description=description, rules=[], dsl="")
|
|
|
|
|
self._state.presets.append(new_preset)
|
|
|
|
|
self._state.selected_name = name
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
self._editor.set_content("")
|
|
|
|
|
self._editor.conf.readonly = False
|
|
|
|
|
self._sync_provider()
|
|
|
|
|
logger.debug(f"Created preset '{name}'")
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
old_name = preset.name
|
|
|
|
|
preset.name = name
|
|
|
|
|
preset.description = description
|
|
|
|
|
self._state.selected_name = name
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
logger.debug(f"Renamed preset '{old_name}' → '{name}'")
|
|
|
|
|
return self.render()
|
|
|
|
|
def handle_confirm_rename(self, client_response):
|
|
|
|
|
name = (client_response.get("name") or "").strip()
|
|
|
|
|
description = (client_response.get("description") or "").strip()
|
|
|
|
|
|
|
|
|
|
def on_cancel(self):
|
|
|
|
|
preset = self._get_user_preset(self._state.selected_name)
|
|
|
|
|
if not name or preset is None:
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
if name != preset.name:
|
|
|
|
|
all_names = {p.name for p in self._get_all_presets()}
|
|
|
|
|
if name in all_names:
|
|
|
|
|
logger.debug(f"Cannot rename to '{name}': name already exists")
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
# === Rendering ===
|
|
|
|
|
old_name = preset.name
|
|
|
|
|
preset.name = name
|
|
|
|
|
preset.description = description
|
|
|
|
|
self._state.selected_name = name
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
self._sync_provider()
|
|
|
|
|
logger.debug(f"Renamed preset '{old_name}' → '{name}'")
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
def _mk_preset_item(self, preset: RulePreset):
|
|
|
|
|
is_active = self._state.selected_name == preset.name
|
|
|
|
|
is_builtin = self._is_builtin(preset.name)
|
|
|
|
|
def handle_cancel(self):
|
|
|
|
|
self._state.ns_mode = "view"
|
|
|
|
|
return self.render()
|
|
|
|
|
|
|
|
|
|
badges = []
|
|
|
|
|
if preset.has_formatter():
|
|
|
|
|
badges.append(Span("format()", cls="badge badge-xs badge-secondary"))
|
|
|
|
|
if preset.has_style():
|
|
|
|
|
badges.append(Span("style()", cls="badge badge-xs badge-primary"))
|
|
|
|
|
if is_builtin:
|
|
|
|
|
badges.append(Span("built-in", cls="badge badge-xs badge-ghost"))
|
|
|
|
|
# === Rendering ===
|
|
|
|
|
|
|
|
|
|
item_cls = "mf-fmgr-preset-item"
|
|
|
|
|
if is_active:
|
|
|
|
|
item_cls += " mf-fmgr-preset-item-active"
|
|
|
|
|
def _get_badges(self, preset: RulePreset, is_builtin: bool):
|
|
|
|
|
badges = []
|
|
|
|
|
if preset.has_formatter():
|
|
|
|
|
badges.append(self._mk_badge("format"))
|
|
|
|
|
if preset.has_style():
|
|
|
|
|
badges.append(self._mk_badge("style"))
|
|
|
|
|
if is_builtin:
|
|
|
|
|
badges.append(self._mk_badge("built-in"))
|
|
|
|
|
|
|
|
|
|
select_cmd = Command(
|
|
|
|
|
"SelectPreset",
|
|
|
|
|
f"Select {preset.name}",
|
|
|
|
|
self,
|
|
|
|
|
self.on_select_preset,
|
|
|
|
|
args=[preset.name],
|
|
|
|
|
).htmx(target=f"#{self._id}", swap="outerHTML")
|
|
|
|
|
return badges
|
|
|
|
|
|
|
|
|
|
return mk.mk(
|
|
|
|
|
Div(
|
|
|
|
|
Div(preset.name, cls="mf-fmgr-preset-name"),
|
|
|
|
|
Div(preset.description, cls="mf-fmgr-preset-desc") if preset.description else None,
|
|
|
|
|
Div(*badges, cls="mf-fmgr-preset-badges") if badges else None,
|
|
|
|
|
cls=item_cls,
|
|
|
|
|
),
|
|
|
|
|
command=select_cmd,
|
|
|
|
|
)
|
|
|
|
|
def _mk_badge(self, text: Literal["format", "style", "built-in"]):
|
|
|
|
|
if text == "built-in":
|
|
|
|
|
return Span("built-in", cls="badge badge-xs badge-ghost")
|
|
|
|
|
if text == "style":
|
|
|
|
|
return Span("style", cls="badge badge-xs badge-primary")
|
|
|
|
|
return Span("format", cls="badge badge-xs badge-secondary")
|
|
|
|
|
|
|
|
|
|
def _mk_preset_header(self, preset: RulePreset, is_builtin: bool):
|
|
|
|
|
badges = []
|
|
|
|
|
if preset.has_formatter():
|
|
|
|
|
badges.append(Span("format()", cls="badge badge-secondary"))
|
|
|
|
|
if preset.has_style():
|
|
|
|
|
badges.append(Span("style()", cls="badge badge-primary"))
|
|
|
|
|
if is_builtin:
|
|
|
|
|
badges.append(Span("built-in", cls="badge badge-ghost"))
|
|
|
|
|
def _mk_preset_item(self, preset: RulePreset):
|
|
|
|
|
is_active = self._state.selected_name == preset.name
|
|
|
|
|
is_builtin = self._is_builtin(preset.name)
|
|
|
|
|
badges = self._get_badges(preset, is_builtin)
|
|
|
|
|
|
|
|
|
|
return Div(
|
|
|
|
|
Div(
|
|
|
|
|
Div(preset.name, cls="text-sm font-semibold"),
|
|
|
|
|
Div(preset.description, cls="text-xs opacity-50") if preset.description else None,
|
|
|
|
|
),
|
|
|
|
|
Div(*badges, cls="flex gap-1") if badges else None,
|
|
|
|
|
cls="mf-fmgr-editor-meta",
|
|
|
|
|
)
|
|
|
|
|
item_cls = "mf-fmgr-preset-item"
|
|
|
|
|
if is_active:
|
|
|
|
|
item_cls += " mf-fmgr-preset-item-active"
|
|
|
|
|
|
|
|
|
|
def _mk_editor_view(self):
|
|
|
|
|
preset = self._get_selected_preset()
|
|
|
|
|
if preset is None:
|
|
|
|
|
return Div("Select a preset to edit", cls="mf-fmgr-placeholder p-4 text-sm opacity-50")
|
|
|
|
|
is_builtin = self._is_builtin(preset.name)
|
|
|
|
|
return Div(
|
|
|
|
|
self._mk_preset_header(preset, is_builtin),
|
|
|
|
|
self._editor,
|
|
|
|
|
cls="mf-fmgr-editor-view",
|
|
|
|
|
)
|
|
|
|
|
return mk.mk(
|
|
|
|
|
Div(
|
|
|
|
|
Div(preset.name, cls="mf-fmgr-preset-name"),
|
|
|
|
|
Div(preset.description, cls="mf-fmgr-preset-desc") if preset.description else None,
|
|
|
|
|
Div(*badges, cls="mf-fmgr-preset-badges") if badges else None,
|
|
|
|
|
cls=item_cls,
|
|
|
|
|
),
|
|
|
|
|
command=self.commands.select_preset(preset.name),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _mk_new_form(self):
|
|
|
|
|
return Div(
|
|
|
|
|
Form(
|
|
|
|
|
Fieldset(
|
|
|
|
|
Label("Name"),
|
|
|
|
|
Input(name="name", cls="input input-sm w-full"),
|
|
|
|
|
Label("Description"),
|
|
|
|
|
Input(name="description", cls="input input-sm w-full"),
|
|
|
|
|
legend="New preset",
|
|
|
|
|
cls="fieldset border-base-300 rounded-box p-4",
|
|
|
|
|
),
|
|
|
|
|
mk.dialog_buttons(
|
|
|
|
|
on_ok=self.commands.confirm_new(),
|
|
|
|
|
on_cancel=self.commands.cancel(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
cls="mf-fmgr-form p-4",
|
|
|
|
|
)
|
|
|
|
|
def _mk_preset_header(self, preset: RulePreset, is_builtin: bool):
|
|
|
|
|
badges = self._get_badges(preset, is_builtin)
|
|
|
|
|
|
|
|
|
|
def _mk_rename_form(self):
|
|
|
|
|
preset = self._get_selected_preset()
|
|
|
|
|
return Div(
|
|
|
|
|
Form(
|
|
|
|
|
Fieldset(
|
|
|
|
|
Label("Name"),
|
|
|
|
|
Input(name="name", value=preset.name if preset else "", cls="input input-sm w-full"),
|
|
|
|
|
Label("Description"),
|
|
|
|
|
Input(name="description", value=preset.description if preset else "", cls="input input-sm w-full"),
|
|
|
|
|
legend="Rename preset",
|
|
|
|
|
cls="fieldset border-base-300 rounded-box p-4",
|
|
|
|
|
),
|
|
|
|
|
mk.dialog_buttons(
|
|
|
|
|
on_ok=self.commands.confirm_rename(),
|
|
|
|
|
on_cancel=self.commands.cancel(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
cls="mf-fmgr-form p-4",
|
|
|
|
|
)
|
|
|
|
|
return Div(
|
|
|
|
|
Div(
|
|
|
|
|
Div(preset.name, cls="text-sm font-semibold"),
|
|
|
|
|
Div(preset.description, cls="text-xs opacity-50") if preset.description else None,
|
|
|
|
|
),
|
|
|
|
|
Div(*badges, cls="flex gap-1") if badges else None,
|
|
|
|
|
cls="mf-fmgr-editor-meta mb-2",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _mk_main_content(self):
|
|
|
|
|
if self._state.ns_mode == "new":
|
|
|
|
|
return self._mk_new_form()
|
|
|
|
|
elif self._state.ns_mode == "rename":
|
|
|
|
|
return self._mk_rename_form()
|
|
|
|
|
return self._mk_editor_view()
|
|
|
|
|
def _mk_editor_view(self):
|
|
|
|
|
preset = self._get_selected_preset()
|
|
|
|
|
if preset is None:
|
|
|
|
|
return Div("Select a preset to edit", cls="mf-fmgr-placeholder p-4 text-sm opacity-50")
|
|
|
|
|
is_builtin = self._is_builtin(preset.name)
|
|
|
|
|
return Div(
|
|
|
|
|
self._mk_preset_header(preset, is_builtin),
|
|
|
|
|
self._editor,
|
|
|
|
|
cls="mf-fmgr-editor-view p-2",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def render(self):
|
|
|
|
|
self._search.set_items(self._get_all_presets())
|
|
|
|
|
self._panel._main = self._mk_main_content()
|
|
|
|
|
self._panel._left = self._search
|
|
|
|
|
def _mk_new_form(self):
|
|
|
|
|
return Div(
|
|
|
|
|
Form(
|
|
|
|
|
Fieldset(
|
|
|
|
|
Label("Name"),
|
|
|
|
|
Input(name="name", cls="input input-sm w-full"),
|
|
|
|
|
Label("Description"),
|
|
|
|
|
Input(name="description", cls="input input-sm w-full"),
|
|
|
|
|
legend="New preset",
|
|
|
|
|
cls="fieldset border-base-300 rounded-box p-2",
|
|
|
|
|
),
|
|
|
|
|
mk.dialog_buttons(
|
|
|
|
|
on_ok=self.commands.confirm_new(),
|
|
|
|
|
on_cancel=self.commands.cancel(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
cls="mf-fmgr-form p-2",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return Div(
|
|
|
|
|
self._menu,
|
|
|
|
|
self._panel,
|
|
|
|
|
id=self._id,
|
|
|
|
|
cls="mf-formatting-manager",
|
|
|
|
|
)
|
|
|
|
|
def _mk_rename_form(self):
|
|
|
|
|
preset = self._get_selected_preset()
|
|
|
|
|
return Div(
|
|
|
|
|
Form(
|
|
|
|
|
Fieldset(
|
|
|
|
|
Label("Name"),
|
|
|
|
|
Input(name="name", value=preset.name if preset else "", cls="input input-sm w-full"),
|
|
|
|
|
Label("Description"),
|
|
|
|
|
Input(name="description", value=preset.description if preset else "", cls="input input-sm w-full"),
|
|
|
|
|
legend="Rename preset",
|
|
|
|
|
cls="fieldset border-base-300 rounded-box p-4",
|
|
|
|
|
),
|
|
|
|
|
mk.dialog_buttons(
|
|
|
|
|
on_ok=self.commands.confirm_rename(),
|
|
|
|
|
on_cancel=self.commands.cancel(),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
cls="mf-fmgr-form p-2",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def __ft__(self):
|
|
|
|
|
return self.render()
|
|
|
|
|
def _mk_main_content(self):
|
|
|
|
|
if self._state.ns_mode == "new":
|
|
|
|
|
return self._mk_new_form()
|
|
|
|
|
elif self._state.ns_mode == "rename":
|
|
|
|
|
return self._mk_rename_form()
|
|
|
|
|
return self._mk_editor_view()
|
|
|
|
|
|
|
|
|
|
def render(self):
|
|
|
|
|
self._search.set_items(self._get_all_presets())
|
|
|
|
|
self._panel._main = self._mk_main_content()
|
|
|
|
|
self._panel._left = self._search
|
|
|
|
|
|
|
|
|
|
return Div(
|
|
|
|
|
self._menu,
|
|
|
|
|
self._panel,
|
|
|
|
|
id=self._id,
|
|
|
|
|
cls="mf-formatting-manager",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def __ft__(self):
|
|
|
|
|
return self.render()
|
|
|
|
|
|