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) formatting_manager = DataGridFormattingManager(layout)
btn_show_formatting_manager = mk.label("Formatting", btn_show_formatting_manager = mk.label("Formatting",
icon=text_edit_style20_regular, icon=text_edit_style20_regular,
command=add_tab("Formatting", formatting_manager), command=add_tab("Formatting", formatting_manager))
id=formatting_manager.get_id())
layout.left_drawer.add(btn_show_formatting_manager, "Parameters") layout.left_drawer.add(btn_show_formatting_manager, "Parameters")
layout.left_drawer.add(btn_file_upload, "Test") 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-border: color-mix(in oklab, var(--color-base-content) 20%, #0000);
--color-resize: color-mix(in oklab, var(--color-base-content) 50%, #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-selection: color-mix(in oklab, var(--color-primary) 20%, #0000);
--color-button-hover: var(--color-base-300);
--datagrid-resize-zindex: 1; --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'; --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 { .mf-button:hover {
background-color: var(--color-base-300); background-color: var(--color-button-hover);
} }
.mf-tooltip-container { .mf-tooltip-container {

View File

@@ -108,6 +108,7 @@
.mf-panel-content { .mf-panel-content {
overflow-y: auto; overflow-y: auto;
margin-top: 0.5rem;
} }
/* Remove padding-top when using title layout */ /* Remove padding-top when using title layout */
@@ -115,3 +116,12 @@
.mf-panel-right.mf-panel-with-title { .mf-panel-right.mf-panel-with-title {
padding-top: 0; 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 { .mf-treenode:hover {
background-color: var(--color-base-200); background-color: var(--color-button-hover);
} }
.mf-treenode.selected { .mf-treenode.selected {

View File

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

View File

@@ -198,7 +198,7 @@ class Panel(MultipleInstance):
body = Div( body = Div(
header, header,
Div(content, id=self._ids.content(side), cls="mf-panel-content"), 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": if side == "left":
return Div( return Div(