Fixed bugs in DataGridFormattingManager

This commit is contained in:
2026-03-14 22:01:44 +01:00
parent 56fb3cf021
commit a4ebd6d61b
8 changed files with 488 additions and 363 deletions

View File

@@ -80,8 +80,7 @@ def index(session):
formatting_manager = DataGridFormattingManager(layout)
btn_show_formatting_manager = mk.label("Formatting",
icon=text_edit_style20_regular,
command=add_tab("Formatting", formatting_manager),
id=formatting_manager.get_id())
command=add_tab("Formatting", formatting_manager))
layout.left_drawer.add(btn_show_formatting_manager, "Parameters")
layout.left_drawer.add(btn_file_upload, "Test")

View File

@@ -0,0 +1,85 @@
/* ============================================= */
/* ======== DataGridFormattingManager ========== */
/* ============================================= */
.mf-formatting-manager {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
/* ---- Preset list items ---- */
.mf-fmgr-preset-item {
display: flex;
flex-direction: column;
gap: 2px;
padding: 6px 8px;
border-radius: var(--radius-md);
cursor: pointer;
border: 1px solid transparent;
margin-bottom: 0.5rem;
transition: background-color 0.1s ease;
}
.mf-fmgr-preset-item:hover {
background-color: var(--color-button-hover);
}
.mf-fmgr-preset-item-active {
background-color: color-mix(in oklab, var(--color-primary) 15%, #0000);
border-color: color-mix(in oklab, var(--color-primary) 40%, #0000);
}
.mf-fmgr-preset-name {
font-size: var(--text-sm);
font-weight: 500;
}
.mf-fmgr-preset-desc {
font-size: var(--text-xs);
opacity: 0.55;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mf-fmgr-preset-badges {
display: flex;
gap: 4px;
margin-top: 2px;
}
/* ---- Editor panel ---- */
.mf-fmgr-editor-view {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.mf-fmgr-editor-meta {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid var(--color-border);
gap: 8px;
flex-shrink: 0;
}
.mf-fmgr-placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
/* ---- New / Rename form ---- */
.mf-fmgr-form {
max-width: 480px;
}

View File

@@ -3,6 +3,7 @@
--color-border: color-mix(in oklab, var(--color-base-content) 20%, #0000);
--color-resize: color-mix(in oklab, var(--color-base-content) 50%, #0000);
--color-selection: color-mix(in oklab, var(--color-primary) 20%, #0000);
--color-button-hover: var(--color-base-300);
--datagrid-resize-zindex: 1;
--font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
@@ -58,7 +59,7 @@
}
.mf-button:hover {
background-color: var(--color-base-300);
background-color: var(--color-button-hover);
}
.mf-tooltip-container {

View File

@@ -108,6 +108,7 @@
.mf-panel-content {
overflow-y: auto;
margin-top: 0.5rem;
}
/* Remove padding-top when using title layout */
@@ -115,3 +116,12 @@
.mf-panel-right.mf-panel-with-title {
padding-top: 0;
}
.mf-panel-body.mf-panel-body-right {
padding-left: 0.5rem;
}
.mf-panel-body.mf-panel-body-left {
padding-right: 0.5rem;
}

View File

@@ -36,7 +36,7 @@
.mf-treenode:hover {
background-color: var(--color-base-200);
background-color: var(--color-button-hover);
}
.mf-treenode.selected {

View File

@@ -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.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.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.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.handle_confirm_new,
).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 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 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 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 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_rename(self):
return Command(
"ConfirmRename",
"Confirm rename",
self._owner,
self._owner.on_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",
)
provider = DatagridMetadataProvider(self)
completion_engine = FormattingCompletionEngine(provider, "")
DslsManager.register(completion_engine, DSLParser())
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._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",
)
self.handle_select_preset(self._state.selected_name)
self._sync_provider()
logger.debug(f"DataGridFormattingManager created with id={self._id}")
def get_main_content_id(self):
return self._panel.get_ids().main
# === Helpers ===
def _get_all_presets(self) -> list:
"""Returns builtin presets followed by user presets."""
return list(DEFAULT_RULE_PRESETS.values()) + self._state.presets
def _is_builtin(self, name: str) -> bool:
return name in DEFAULT_RULE_PRESETS
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_user_preset(self, name: str) -> Optional[RulePreset]:
for p in self._state.presets:
if p.name == name:
return p
return None
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 []
logger.debug(f"DataGridFormattingManager created with id={self._id}")
# === Helpers ===
def _get_all_presets(self) -> list:
"""Returns builtin presets followed by user presets."""
return list(DEFAULT_RULE_PRESETS.values()) + self._state.presets
def _is_builtin(self, name: str) -> bool:
return name in DEFAULT_RULE_PRESETS
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_user_preset(self, name: str) -> Optional[RulePreset]:
for p in self._state.presets:
if p.name == name:
return p
return None
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 []
# === Command handlers ===
def on_new_preset(self):
self._state.ns_mode = "new"
return self.render()
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 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()
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 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 on_confirm_new(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()
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()
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 on_confirm_rename(self, client_response):
name = (client_response.get("name") or "").strip()
description = (client_response.get("description") or "").strip()
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()
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 on_cancel(self):
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()}
# === Command handlers ===
def handle_new_preset(self):
self._state.ns_mode = "new"
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 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()
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()
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
def handle_confirm_new(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()
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()
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()
def handle_confirm_rename(self, client_response):
name = (client_response.get("name") or "").strip()
description = (client_response.get("description") or "").strip()
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 ===
def _mk_preset_item(self, preset: RulePreset):
is_active = self._state.selected_name == preset.name
is_builtin = self._is_builtin(preset.name)
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"))
item_cls = "mf-fmgr-preset-item"
if is_active:
item_cls += " mf-fmgr-preset-item-active"
select_cmd = Command(
"SelectPreset",
f"Select {preset.name}",
self,
self.on_select_preset,
args=[preset.name],
).htmx(target=f"#{self._id}", swap="outerHTML")
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_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"))
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",
)
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",
)
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_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",
)
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()
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 handle_cancel(self):
self._state.ns_mode = "view"
return self.render()
# === Rendering ===
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"))
return badges
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_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)
item_cls = "mf-fmgr-preset-item"
if is_active:
item_cls += " mf-fmgr-preset-item-active"
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_preset_header(self, preset: RulePreset, is_builtin: bool):
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 mb-2",
)
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 _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",
)
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 _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()

View File

@@ -1,3 +1,4 @@
import inspect
from dataclasses import dataclass, field
from typing import Optional
@@ -35,7 +36,8 @@ class Menu(MultipleInstance):
name
for name in dir(commands_obj)
if not name.startswith("_")
and callable(getattr(commands_obj, name))
and callable(attr := getattr(commands_obj, name))
and len(inspect.signature(attr).parameters) == 0
]
return {
@@ -59,7 +61,7 @@ class Menu(MultipleInstance):
Div("|"),
*[self._mk_menu(command_name) for command_name in self._state.last_used[:3]]
) if self._state.last_used else [],
cls="flex"
cls="flex mb-1"
),
id=self._id
)

View File

@@ -198,7 +198,7 @@ class Panel(MultipleInstance):
body = Div(
header,
Div(content, id=self._ids.content(side), cls="mf-panel-content"),
cls="mf-panel-body"
cls=f"mf-panel-body mf-panel-body-{side}"
)
if side == "left":
return Div(