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): def save_preset(self):
return Command( return Command(
"SavePreset", "SavePreset",
"Save preset", "Save preset",
self._owner, self._owner,
self._owner.on_save_preset, self._owner.handle_save_preset,
icon=IconsHelper.get("Save20Regular"), icon=IconsHelper.get("Save20Regular"),
).htmx(target=f"#{self._id}", swap="outerHTML") ).htmx(target=f"#{self._id}", swap="outerHTML")
def rename_preset(self): def rename_preset(self):
return Command( return Command(
"RenamePreset", "RenamePreset",
"Rename preset", "Rename preset",
self._owner, self._owner,
self._owner.on_rename_preset, self._owner.handle_rename_preset,
icon=IconsHelper.get("Rename20Regular"), icon=IconsHelper.get("edit20_regular"),
).htmx(target=f"#{self._id}", swap="outerHTML") ).htmx(target=f"#{self._id}", swap="outerHTML")
def delete_preset(self): def delete_preset(self):
return Command( return Command(
"DeletePreset", "DeletePreset",
"Delete preset", "Delete preset",
self._owner, self._owner,
self._owner.on_delete_preset, self._owner.handle_delete_preset,
icon=IconsHelper.get("Delete20Regular"), icon=IconsHelper.get("Delete20Regular"),
).htmx(target=f"#{self._id}", swap="outerHTML") ).htmx(target=f"#{self._id}", swap="outerHTML")
def confirm_new(self): def confirm_new(self):
return Command( return Command(
"ConfirmNew", "ConfirmNew",
"Confirm new preset", "Confirm new preset",
self._owner, self._owner,
self._owner.on_confirm_new, self._owner.handle_confirm_new,
).htmx(target=f"#{self._id}", swap="outerHTML") ).htmx(target=f"#{self._id}", swap="outerHTML")
def confirm_rename(self): def confirm_rename(self):
return Command( return Command(
"ConfirmRename", "ConfirmRename",
"Confirm rename", "Confirm rename",
self._owner, self._owner,
self._owner.on_confirm_rename, self._owner.handle_confirm_rename,
).htmx(target=f"#{self._id}", swap="outerHTML") ).htmx(target=f"#{self._id}", swap="outerHTML")
def cancel(self): def cancel(self):
return Command( return Command(
"Cancel", "Cancel",
"Cancel", "Cancel",
self._owner, self._owner,
self._owner.on_cancel, self._owner.handle_cancel,
).htmx(target=f"#{self._id}", swap="outerHTML") ).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): 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._panel = Panel(
self, self,
conf=PanelConf(left=True, right=False, left_title="Presets"), conf=PanelConf(left=True, right=False, left_title="Presets"),
_id="-panel", _id="-panel",
) )
self._menu = Menu( self._menu = Menu(
self, self,
conf=MenuConf(fixed_items=["NewPreset", "SavePreset", "RenamePreset", "DeletePreset"]), conf=MenuConf(fixed_items=["NewPreset", "SavePreset", "RenamePreset", "DeletePreset"]),
save_state=False, save_state=False,
_id="-menu", _id="-menu",
) )
self._search = Search( self._search = Search(
self, self,
items_names="Presets", items_names="Presets",
template=self._mk_preset_item, template=self._mk_preset_item,
_id="-search", _id="-search",
) )
self._editor = DslEditor( provider = DatagridMetadataProvider(self)
self, completion_engine = FormattingCompletionEngine(provider, "")
dsl=FormattingDSL(), DslsManager.register(completion_engine, DSLParser())
conf=DslEditorConf(
save_button=False,
autocompletion=False,
linting=False,
placeholder=_DSL_PLACEHOLDER,
),
save_state=False,
_id="-editor",
)
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: def get_main_content_id(self):
"""Returns builtin presets followed by user presets.""" return self._panel.get_ids().main
return list(DEFAULT_RULE_PRESETS.values()) + self._state.presets
def _is_builtin(self, name: str) -> bool: # === Helpers ===
return name in DEFAULT_RULE_PRESETS
def _get_selected_preset(self) -> Optional[RulePreset]: def _get_all_presets(self) -> list:
if not self._state.selected_name: """Returns builtin presets followed by user presets."""
return None return list(DEFAULT_RULE_PRESETS.values()) + self._state.presets
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]: def _is_builtin(self, name: str) -> bool:
for p in self._state.presets: return name in DEFAULT_RULE_PRESETS
if p.name == name:
return p
return None
def _parse_dsl_to_rules(self, dsl_text: str) -> list: def _get_selected_preset(self) -> Optional[RulePreset]:
"""Parse DSL text and extract FormatRule objects, ignoring scopes.""" if not self._state.selected_name:
try: return None
scoped_rules = parse_dsl(dsl_text) for p in self._get_all_presets():
return [sr.rule for sr in scoped_rules] if p.name == self._state.selected_name:
except Exception: return p
return [] 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): def _parse_dsl_to_rules(self, dsl_text: str) -> list:
self._state.ns_mode = "new" """Parse DSL text and extract FormatRule objects, ignoring scopes."""
return self.render() try:
scoped_rules = parse_dsl(dsl_text)
return [sr.rule for sr in scoped_rules]
except Exception:
return []
def on_save_preset(self): def _sync_provider(self):
if not self._state.selected_name or self._is_builtin(self._state.selected_name): """Sync all presets (builtin + user) into the session-scoped metadata provider."""
return self.render() provider = DatagridMetadataProvider(self)
preset = self._get_user_preset(self._state.selected_name) provider.rule_presets = {p.name: p for p in self._get_all_presets()}
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): # === Command handlers ===
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): def handle_new_preset(self):
if not self._state.selected_name or self._is_builtin(self._state.selected_name): self._state.ns_mode = "new"
return self.render() 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): def handle_save_preset(self):
preset = None if not self._state.selected_name or self._is_builtin(self._state.selected_name):
for p in self._get_all_presets(): return self.render()
if p.name == name: preset = self._get_user_preset(self._state.selected_name)
preset = p if preset is None:
break return self.render()
if preset is None: dsl = self._editor.get_content()
return self.render() preset.dsl = dsl
self._state.selected_name = name preset.rules = self._parse_dsl_to_rules(dsl)
self._state.ns_mode = "view" self._state.save()
self._editor.set_content(preset.dsl) self._sync_provider()
self._editor.conf.readonly = self._is_builtin(name) logger.debug(f"Saved preset '{preset.name}' with {len(preset.rules)} rules")
logger.debug(f"Selected preset '{name}', readonly={self._editor.conf.readonly}") return self.render()
return self.render()
def on_confirm_new(self, client_response): def handle_rename_preset(self):
name = (client_response.get("name") or "").strip() if not self._state.selected_name or self._is_builtin(self._state.selected_name):
description = (client_response.get("description") or "").strip() return self.render()
self._state.ns_mode = "rename"
return self.render()
if not name: def handle_delete_preset(self):
self._state.ns_mode = "view" if not self._state.selected_name or self._is_builtin(self._state.selected_name):
return self.render() 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()} def handle_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
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="") def handle_confirm_new(self, client_response):
self._state.presets.append(new_preset) name = (client_response.get("name") or "").strip()
self._state.selected_name = name description = (client_response.get("description") or "").strip()
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): if not name:
name = (client_response.get("name") or "").strip() self._state.ns_mode = "view"
description = (client_response.get("description") or "").strip() return self.render()
preset = self._get_user_preset(self._state.selected_name) all_names = {p.name for p in self._get_all_presets()}
if not name or preset is None: if name in all_names:
self._state.ns_mode = "view" logger.debug(f"Cannot create preset '{name}': name already exists")
return self.render() self._state.ns_mode = "view"
return self.render()
if name != preset.name: new_preset = RulePreset(name=name, description=description, rules=[], dsl="")
all_names = {p.name for p in self._get_all_presets()} self._state.presets.append(new_preset)
if name in all_names: self._state.selected_name = name
logger.debug(f"Cannot rename to '{name}': name already exists") self._state.ns_mode = "view"
self._state.ns_mode = "view" self._editor.set_content("")
return self.render() self._editor.conf.readonly = False
self._sync_provider()
logger.debug(f"Created preset '{name}'")
return self.render()
old_name = preset.name def handle_confirm_rename(self, client_response):
preset.name = name name = (client_response.get("name") or "").strip()
preset.description = description description = (client_response.get("description") or "").strip()
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): 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" self._state.ns_mode = "view"
return self.render() 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): def handle_cancel(self):
is_active = self._state.selected_name == preset.name self._state.ns_mode = "view"
is_builtin = self._is_builtin(preset.name) return self.render()
badges = [] # === Rendering ===
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" def _get_badges(self, preset: RulePreset, is_builtin: bool):
if is_active: badges = []
item_cls += " mf-fmgr-preset-item-active" 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( return badges
"SelectPreset",
f"Select {preset.name}",
self,
self.on_select_preset,
args=[preset.name],
).htmx(target=f"#{self._id}", swap="outerHTML")
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_header(self, preset: RulePreset, is_builtin: bool): def _mk_preset_item(self, preset: RulePreset):
badges = [] is_active = self._state.selected_name == preset.name
if preset.has_formatter(): is_builtin = self._is_builtin(preset.name)
badges.append(Span("format()", cls="badge badge-secondary")) badges = self._get_badges(preset, is_builtin)
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( item_cls = "mf-fmgr-preset-item"
Div( if is_active:
Div(preset.name, cls="text-sm font-semibold"), item_cls += " mf-fmgr-preset-item-active"
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): return mk.mk(
preset = self._get_selected_preset() Div(
if preset is None: Div(preset.name, cls="mf-fmgr-preset-name"),
return Div("Select a preset to edit", cls="mf-fmgr-placeholder p-4 text-sm opacity-50") Div(preset.description, cls="mf-fmgr-preset-desc") if preset.description else None,
is_builtin = self._is_builtin(preset.name) Div(*badges, cls="mf-fmgr-preset-badges") if badges else None,
return Div( cls=item_cls,
self._mk_preset_header(preset, is_builtin), ),
self._editor, command=self.commands.select_preset(preset.name),
cls="mf-fmgr-editor-view", )
)
def _mk_new_form(self): def _mk_preset_header(self, preset: RulePreset, is_builtin: bool):
return Div( badges = self._get_badges(preset, is_builtin)
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): return Div(
preset = self._get_selected_preset() Div(
return Div( Div(preset.name, cls="text-sm font-semibold"),
Form( Div(preset.description, cls="text-xs opacity-50") if preset.description else None,
Fieldset( ),
Label("Name"), Div(*badges, cls="flex gap-1") if badges else None,
Input(name="name", value=preset.name if preset else "", cls="input input-sm w-full"), cls="mf-fmgr-editor-meta mb-2",
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): def _mk_editor_view(self):
if self._state.ns_mode == "new": preset = self._get_selected_preset()
return self._mk_new_form() if preset is None:
elif self._state.ns_mode == "rename": return Div("Select a preset to edit", cls="mf-fmgr-placeholder p-4 text-sm opacity-50")
return self._mk_rename_form() is_builtin = self._is_builtin(preset.name)
return self._mk_editor_view() return Div(
self._mk_preset_header(preset, is_builtin),
self._editor,
cls="mf-fmgr-editor-view p-2",
)
def render(self): def _mk_new_form(self):
self._search.set_items(self._get_all_presets()) return Div(
self._panel._main = self._mk_main_content() Form(
self._panel._left = self._search 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( def _mk_rename_form(self):
self._menu, preset = self._get_selected_preset()
self._panel, return Div(
id=self._id, Form(
cls="mf-formatting-manager", 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): def _mk_main_content(self):
return self.render() 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 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(