Add register_sx_layout infrastructure, convert account/federation/orders
Phase 0: Add _ctx_to_env() and render_to_sx_with_env() to shared/sx/helpers.py, register_sx_layout() to shared/sx/layouts.py, and ~root-header/~root-mobile wrapper defcomps to layout.sx. Convert built-in "root" layout to .sx. Phases 1-3: Convert account (65→19 lines), federation (105→97 lines), and orders (88→21 lines) to use register_sx_layout with .sx defcomps that read ctx values as free variables from the evaluation environment. No more Python building SX strings via SxExpr(await root_header_sx(ctx)). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
27
account/sx/layouts.sx
Normal file
27
account/sx/layouts.sx
Normal file
@@ -0,0 +1,27 @@
|
||||
;; Account layout defcomps — read ctx values from env free variables.
|
||||
;; Registered via register_sx_layout("account", ...) in __init__.py.
|
||||
|
||||
;; Full page: root header + auth header row in header-child
|
||||
(defcomp ~account-layout-full ()
|
||||
(<> (~root-header)
|
||||
(~header-child-sx
|
||||
:inner (~auth-header-row :account-url account-url
|
||||
:select-colours select-colours
|
||||
:account-nav account-nav))))
|
||||
|
||||
;; OOB (HTMX): auth row + root header, both with oob=true
|
||||
(defcomp ~account-layout-oob ()
|
||||
(<> (~auth-header-row :account-url account-url
|
||||
:select-colours select-colours
|
||||
:account-nav account-nav
|
||||
:oob true)
|
||||
(~root-header :oob true)))
|
||||
|
||||
;; Mobile menu: auth section + root nav
|
||||
(defcomp ~account-layout-mobile ()
|
||||
(<> (~mobile-menu-section
|
||||
:label "account" :href "/" :level 1 :colour "sky"
|
||||
:items (~auth-nav-items :account-url account-url
|
||||
:select-colours select-colours
|
||||
:account-nav account-nav))
|
||||
(~root-mobile)))
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Account defpage setup — registers layouts and loads .sx pages."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def setup_account_pages() -> None:
|
||||
"""Register account-specific layouts and load page definitions."""
|
||||
@@ -16,76 +14,6 @@ def _load_account_page_files() -> None:
|
||||
load_page_dir(os.path.dirname(__file__), "account")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Layouts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_account_layouts() -> None:
|
||||
from shared.sx.layouts import register_custom_layout
|
||||
register_custom_layout("account", _account_full, _account_oob, _account_mobile)
|
||||
|
||||
|
||||
async def _account_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, header_child_sx, render_to_sx
|
||||
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
auth_hdr = await render_to_sx("auth-header-row",
|
||||
account_url=_call_url(ctx, "account_url", ""),
|
||||
select_colours=ctx.get("select_colours", ""),
|
||||
account_nav=_as_sx_nav(ctx),
|
||||
)
|
||||
hdr_child = await header_child_sx(auth_hdr)
|
||||
return "(<> " + root_hdr + " " + hdr_child + ")"
|
||||
|
||||
|
||||
async def _account_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, render_to_sx
|
||||
|
||||
auth_hdr = await render_to_sx("auth-header-row",
|
||||
account_url=_call_url(ctx, "account_url", ""),
|
||||
select_colours=ctx.get("select_colours", ""),
|
||||
account_nav=_as_sx_nav(ctx),
|
||||
oob=True,
|
||||
)
|
||||
return "(<> " + auth_hdr + " " + await root_header_sx(ctx, oob=True) + ")"
|
||||
|
||||
|
||||
async def _account_mobile(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import mobile_menu_sx, mobile_root_nav_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
ctx = _inject_account_nav(ctx)
|
||||
nav_items = await render_to_sx("auth-nav-items",
|
||||
account_url=_call_url(ctx, "account_url", ""),
|
||||
select_colours=ctx.get("select_colours", ""),
|
||||
account_nav=_as_sx_nav(ctx),
|
||||
)
|
||||
auth_section = await render_to_sx("mobile-menu-section",
|
||||
label="account", href="/", level=1, colour="sky",
|
||||
items=SxExpr(nav_items))
|
||||
return mobile_menu_sx(auth_section, await mobile_root_nav_sx(ctx))
|
||||
|
||||
|
||||
def _call_url(ctx: dict, key: str, path: str = "/") -> str:
|
||||
fn = ctx.get(key)
|
||||
if callable(fn):
|
||||
return fn(path)
|
||||
return str(fn or "") + path
|
||||
|
||||
|
||||
def _inject_account_nav(ctx: dict) -> dict:
|
||||
"""Ensure account_nav is in ctx from g.account_nav."""
|
||||
if "account_nav" not in ctx:
|
||||
from quart import g
|
||||
ctx = dict(ctx)
|
||||
ctx["account_nav"] = getattr(g, "account_nav", "")
|
||||
return ctx
|
||||
|
||||
|
||||
def _as_sx_nav(ctx: dict) -> Any:
|
||||
"""Convert account_nav fragment to SxExpr for use in component calls."""
|
||||
from shared.sx.helpers import _as_sx
|
||||
ctx = _inject_account_nav(ctx)
|
||||
return _as_sx(ctx.get("account_nav"))
|
||||
|
||||
|
||||
from shared.sx.layouts import register_sx_layout
|
||||
register_sx_layout("account", "account-layout-full", "account-layout-oob", "account-layout-mobile")
|
||||
|
||||
17
federation/sx/layouts.sx
Normal file
17
federation/sx/layouts.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
;; Federation layout defcomps — read ctx values from env free variables.
|
||||
;; `actor` is injected into env by the layout registration in __init__.py.
|
||||
|
||||
;; Full page: root header + social header in header-child
|
||||
(defcomp ~social-layout-full ()
|
||||
(<> (~root-header)
|
||||
(~header-child-sx
|
||||
:inner (~federation-social-header
|
||||
:nav (~federation-social-nav :actor actor)))))
|
||||
|
||||
;; OOB (HTMX): social header oob + root header oob
|
||||
(defcomp ~social-layout-oob ()
|
||||
(<> (~oob-header-sx
|
||||
:parent-id "root-header-child"
|
||||
:row (~federation-social-header
|
||||
:nav (~federation-social-nav :actor actor)))
|
||||
(~root-header :oob true)))
|
||||
@@ -17,7 +17,7 @@ def _load_federation_page_files() -> None:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Layouts
|
||||
# Layouts — .sx defcomps read free variables from env
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_federation_layouts() -> None:
|
||||
@@ -25,36 +25,30 @@ def _register_federation_layouts() -> None:
|
||||
register_custom_layout("social", _social_full, _social_oob)
|
||||
|
||||
|
||||
async def _social_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, header_child_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
def _actor_data(ctx: dict) -> dict | None:
|
||||
actor = ctx.get("actor")
|
||||
actor_data = _serialize_actor(actor) if actor else None
|
||||
nav = await render_to_sx("federation-social-nav", actor=actor_data)
|
||||
social_hdr = await render_to_sx("federation-social-header", nav=SxExpr(nav))
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
child = await header_child_sx(social_hdr)
|
||||
return "(<> " + root_hdr + " " + child + ")"
|
||||
if not actor:
|
||||
return None
|
||||
from services.federation_page import _serialize_actor
|
||||
return _serialize_actor(actor)
|
||||
|
||||
|
||||
async def _social_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
env = _ctx_to_env(ctx)
|
||||
env["actor"] = kw.get("actor") or _actor_data(ctx)
|
||||
return await render_to_sx_with_env("social-layout-full", env)
|
||||
|
||||
|
||||
async def _social_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
actor = ctx.get("actor")
|
||||
actor_data = _serialize_actor(actor) if actor else None
|
||||
nav = await render_to_sx("federation-social-nav", actor=actor_data)
|
||||
social_hdr = await render_to_sx("federation-social-header", nav=SxExpr(nav))
|
||||
child_oob = await render_to_sx("oob-header-sx",
|
||||
parent_id="root-header-child",
|
||||
row=SxExpr(social_hdr))
|
||||
root_hdr_oob = await root_header_sx(ctx, oob=True)
|
||||
return "(<> " + child_oob + " " + root_hdr_oob + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
env = _ctx_to_env(ctx, oob=True)
|
||||
env["actor"] = kw.get("actor") or _actor_data(ctx)
|
||||
return await render_to_sx_with_env("social-layout-oob", env)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Serializers and helpers — still used by layouts and route handlers
|
||||
# Helpers still used by route handlers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _serialize_actor(actor) -> dict | None:
|
||||
@@ -78,16 +72,12 @@ def _serialize_remote_actor(a) -> dict:
|
||||
async def _social_page(ctx: dict, actor, *, content: str,
|
||||
title: str = "Rose Ash", meta_html: str = "") -> str:
|
||||
"""Build a full social page with social header."""
|
||||
from shared.sx.helpers import render_to_sx, root_header_sx, header_child_sx, full_page_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, full_page_sx
|
||||
from markupsafe import escape
|
||||
|
||||
actor_data = _serialize_actor(actor)
|
||||
nav = await render_to_sx("federation-social-nav", actor=actor_data)
|
||||
social_hdr = await render_to_sx("federation-social-header", nav=SxExpr(nav))
|
||||
hdr = await root_header_sx(ctx)
|
||||
child = await header_child_sx(social_hdr)
|
||||
header_rows = "(<> " + hdr + " " + child + ")"
|
||||
env = _ctx_to_env(ctx)
|
||||
env["actor"] = _serialize_actor(actor) if actor else None
|
||||
header_rows = await render_to_sx_with_env("social-layout-full", env)
|
||||
return await full_page_sx(ctx, header_rows=header_rows, content=content,
|
||||
meta_html=meta_html or f'<title>{escape(title)}</title>')
|
||||
|
||||
|
||||
49
orders/sx/layouts.sx
Normal file
49
orders/sx/layouts.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
;; Orders layout defcomps — read ctx values from env free variables.
|
||||
;; Registered via register_sx_layout("orders", ...) in __init__.py.
|
||||
|
||||
;; --- orders layout: root + auth + orders rows ---
|
||||
|
||||
(defcomp ~orders-layout-full (&key list-url)
|
||||
(<> (~root-header)
|
||||
(~header-child-sx
|
||||
:inner (<> (~auth-header-row :account-url account-url
|
||||
:select-colours select-colours
|
||||
:account-nav account-nav)
|
||||
(~orders-header-row :list-url (or list-url "/"))))))
|
||||
|
||||
(defcomp ~orders-layout-oob (&key list-url)
|
||||
(<> (~auth-header-row :account-url account-url
|
||||
:select-colours select-colours
|
||||
:account-nav account-nav
|
||||
:oob true)
|
||||
(~oob-header-sx
|
||||
:parent-id "auth-header-child"
|
||||
:row (~orders-header-row :list-url (or list-url "/")))
|
||||
(~root-header :oob true)))
|
||||
|
||||
(defcomp ~orders-layout-mobile ()
|
||||
(~root-mobile))
|
||||
|
||||
;; --- order-detail layout: root + auth + orders + order rows ---
|
||||
|
||||
(defcomp ~order-detail-layout-full (&key list-url detail-url)
|
||||
(<> (~root-header)
|
||||
(~order-detail-header-stack
|
||||
:auth (~auth-header-row :account-url account-url
|
||||
:select-colours select-colours
|
||||
:account-nav account-nav)
|
||||
:orders (~orders-header-row :list-url (or list-url "/"))
|
||||
:order (~menu-row-sx :id "order-row" :level 3 :colour "sky"
|
||||
:link-href (or detail-url "/") :link-label "Order"
|
||||
:icon "fa fa-gbp"))))
|
||||
|
||||
(defcomp ~order-detail-layout-oob (&key detail-url)
|
||||
(<> (~oob-header-sx
|
||||
:parent-id "orders-header-child"
|
||||
:row (~menu-row-sx :id "order-row" :level 3 :colour "sky"
|
||||
:link-href (or detail-url "/") :link-label "Order"
|
||||
:icon "fa fa-gbp" :oob true))
|
||||
(~root-header :oob true)))
|
||||
|
||||
(defcomp ~order-detail-layout-mobile ()
|
||||
(~root-mobile))
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Orders defpage setup — registers layouts and loads .sx pages."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def setup_orders_pages() -> None:
|
||||
"""Register orders-specific layouts and load page definitions."""
|
||||
@@ -17,110 +15,7 @@ def _load_orders_page_files() -> None:
|
||||
load_page_dir(os.path.dirname(__file__), "orders")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Layouts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_orders_layouts() -> None:
|
||||
from shared.sx.layouts import register_custom_layout
|
||||
register_custom_layout("orders", _orders_full, _orders_oob, _orders_mobile)
|
||||
register_custom_layout("order-detail", _order_detail_full, _order_detail_oob, _order_detail_mobile)
|
||||
|
||||
|
||||
async def _orders_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, header_child_sx, call_url, render_to_sx
|
||||
|
||||
list_url = kw.get("list_url", "/")
|
||||
account_url = call_url(ctx, "account_url", "")
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
auth_hdr = await render_to_sx("auth-header-row",
|
||||
account_url=account_url,
|
||||
select_colours=ctx.get("select_colours", ""),
|
||||
account_nav=_as_sx_nav(ctx),
|
||||
)
|
||||
orders_hdr = await render_to_sx("orders-header-row", list_url=list_url)
|
||||
inner = "(<> " + auth_hdr + " " + orders_hdr + ")"
|
||||
return "(<> " + root_hdr + " " + await header_child_sx(inner) + ")"
|
||||
|
||||
|
||||
async def _orders_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, render_to_sx
|
||||
from shared.sx.helpers import call_url
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
list_url = kw.get("list_url", "/")
|
||||
account_url = call_url(ctx, "account_url", "")
|
||||
auth_hdr = await render_to_sx("auth-header-row",
|
||||
account_url=account_url,
|
||||
select_colours=ctx.get("select_colours", ""),
|
||||
account_nav=_as_sx_nav(ctx),
|
||||
oob=True,
|
||||
)
|
||||
orders_hdr = await render_to_sx("orders-header-row", list_url=list_url)
|
||||
auth_child_oob = await render_to_sx("oob-header-sx",
|
||||
parent_id="auth-header-child",
|
||||
row=SxExpr(orders_hdr))
|
||||
root_hdr = await root_header_sx(ctx, oob=True)
|
||||
return "(<> " + auth_hdr + " " + auth_child_oob + " " + root_hdr + ")"
|
||||
|
||||
|
||||
async def _orders_mobile(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import mobile_menu_sx, mobile_root_nav_sx
|
||||
return mobile_menu_sx(await mobile_root_nav_sx(ctx))
|
||||
|
||||
|
||||
async def _order_detail_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, render_to_sx
|
||||
from shared.sx.helpers import call_url
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
list_url = kw.get("list_url", "/")
|
||||
detail_url = kw.get("detail_url", "/")
|
||||
account_url = call_url(ctx, "account_url", "")
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
order_row = await render_to_sx(
|
||||
"menu-row-sx",
|
||||
id="order-row", level=3, colour="sky", link_href=detail_url,
|
||||
link_label="Order", icon="fa fa-gbp",
|
||||
)
|
||||
auth_hdr = await render_to_sx("auth-header-row",
|
||||
account_url=account_url,
|
||||
select_colours=ctx.get("select_colours", ""),
|
||||
account_nav=_as_sx_nav(ctx),
|
||||
)
|
||||
orders_hdr = await render_to_sx("orders-header-row", list_url=list_url)
|
||||
detail_header = await render_to_sx(
|
||||
"order-detail-header-stack",
|
||||
auth=SxExpr(auth_hdr),
|
||||
orders=SxExpr(orders_hdr),
|
||||
order=SxExpr(order_row),
|
||||
)
|
||||
return "(<> " + root_hdr + " " + detail_header + ")"
|
||||
|
||||
|
||||
async def _order_detail_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
detail_url = kw.get("detail_url", "/")
|
||||
order_row_oob = await render_to_sx(
|
||||
"menu-row-sx",
|
||||
id="order-row", level=3, colour="sky", link_href=detail_url,
|
||||
link_label="Order", icon="fa fa-gbp", oob=True,
|
||||
)
|
||||
header_child_oob = await render_to_sx("oob-header-sx",
|
||||
parent_id="orders-header-child",
|
||||
row=SxExpr(order_row_oob))
|
||||
root_hdr = await root_header_sx(ctx, oob=True)
|
||||
return "(<> " + header_child_oob + " " + root_hdr + ")"
|
||||
|
||||
|
||||
async def _order_detail_mobile(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import mobile_menu_sx, mobile_root_nav_sx
|
||||
return mobile_menu_sx(await mobile_root_nav_sx(ctx))
|
||||
|
||||
|
||||
def _as_sx_nav(ctx: dict) -> Any:
|
||||
"""Convert account_nav fragment to SxExpr for use in component calls."""
|
||||
from shared.sx.helpers import _as_sx
|
||||
return _as_sx(ctx.get("account_nav"))
|
||||
from shared.sx.layouts import register_sx_layout
|
||||
register_sx_layout("orders", "orders-layout-full", "orders-layout-oob", "orders-layout-mobile")
|
||||
register_sx_layout("order-detail", "order-detail-layout-full", "order-detail-layout-oob", "order-detail-layout-mobile")
|
||||
|
||||
@@ -385,6 +385,59 @@ def _build_component_ast(__name: str, **kwargs: Any) -> list:
|
||||
return ast
|
||||
|
||||
|
||||
def _ctx_to_env(ctx: dict, *, oob: bool = False) -> dict:
|
||||
"""Convert template context dict → SX evaluation env dict.
|
||||
|
||||
Applies ``_as_sx()`` to HTML fragments, ``call_url()`` to URL helpers,
|
||||
extracts rights/admin flags. Returns kebab-case keys matching SX
|
||||
symbol conventions so .sx defcomps can read them as free variables.
|
||||
"""
|
||||
rights = ctx.get("rights") or {}
|
||||
is_admin = rights.get("admin") if isinstance(rights, dict) else getattr(rights, "admin", False)
|
||||
env = {
|
||||
# Root header values (match ~header-row-sx &key params)
|
||||
"cart-mini": _as_sx(ctx.get("cart_mini")),
|
||||
"blog-url": call_url(ctx, "blog_url", ""),
|
||||
"site-title": ctx.get("base_title", ""),
|
||||
"app-label": ctx.get("app_label", ""),
|
||||
"nav-tree": _as_sx(ctx.get("nav_tree")),
|
||||
"auth-menu": _as_sx(ctx.get("auth_menu")),
|
||||
"nav-panel": _as_sx(ctx.get("nav_panel")),
|
||||
"settings-url": call_url(ctx, "blog_url", "/settings/") if is_admin else "",
|
||||
"is-admin": is_admin,
|
||||
"oob": oob,
|
||||
# URL helpers (pre-resolved to strings)
|
||||
"account-url": call_url(ctx, "account_url", ""),
|
||||
"events-url": call_url(ctx, "events_url", ""),
|
||||
"market-url": call_url(ctx, "market_url", ""),
|
||||
"cart-url": call_url(ctx, "cart_url", ""),
|
||||
# Common values
|
||||
"select-colours": ctx.get("select_colours", ""),
|
||||
"rights": rights,
|
||||
# Fragments (used by various services)
|
||||
"container-nav": _as_sx(ctx.get("container_nav")),
|
||||
"account-nav": _as_sx(ctx.get("account_nav")),
|
||||
# Post context
|
||||
"post": ctx.get("post") or {},
|
||||
}
|
||||
return env
|
||||
|
||||
|
||||
async def render_to_sx_with_env(__name: str, extra_env: dict, **kwargs: Any) -> str:
|
||||
"""Like ``render_to_sx`` but merges *extra_env* into the evaluation
|
||||
environment before eval. Used by ``register_sx_layout`` so .sx
|
||||
defcomps can read ctx values as free variables.
|
||||
"""
|
||||
from .jinja_bridge import get_component_env, _get_request_context
|
||||
from .async_eval import async_eval_to_sx
|
||||
|
||||
ast = _build_component_ast(__name, **kwargs)
|
||||
env = dict(get_component_env())
|
||||
env.update(extra_env)
|
||||
ctx = _get_request_context()
|
||||
return await async_eval_to_sx(ast, env, ctx)
|
||||
|
||||
|
||||
async def render_to_sx(__name: str, **kwargs: Any) -> str:
|
||||
"""Call a defcomp and get SX wire format back. No SX string literals.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from typing import Any, Callable, Awaitable
|
||||
|
||||
from .helpers import (
|
||||
root_header_sx, post_header_sx, post_admin_header_sx,
|
||||
oob_header_sx, header_child_sx,
|
||||
oob_header_sx,
|
||||
mobile_menu_sx, mobile_root_nav_sx,
|
||||
post_mobile_nav_sx, post_admin_mobile_nav_sx,
|
||||
)
|
||||
@@ -87,15 +87,6 @@ def get_layout(name: str) -> Layout | None:
|
||||
# Built-in layouts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _root_full(ctx: dict, **kw: Any) -> str:
|
||||
return await root_header_sx(ctx)
|
||||
|
||||
|
||||
async def _root_oob(ctx: dict, **kw: Any) -> str:
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
return await oob_header_sx("root-header-child", "root-header-child", root_hdr)
|
||||
|
||||
|
||||
async def _post_full(ctx: dict, **kw: Any) -> str:
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
post_hdr = await post_header_sx(ctx)
|
||||
@@ -127,10 +118,6 @@ async def _post_admin_oob(ctx: dict, **kw: Any) -> str:
|
||||
return "(<> " + post_hdr + " " + admin_oob + ")"
|
||||
|
||||
|
||||
async def _root_mobile(ctx: dict, **kw: Any) -> str:
|
||||
return await mobile_root_nav_sx(ctx)
|
||||
|
||||
|
||||
async def _post_mobile(ctx: dict, **kw: Any) -> str:
|
||||
return mobile_menu_sx(await post_mobile_nav_sx(ctx), await mobile_root_nav_sx(ctx))
|
||||
|
||||
@@ -145,11 +132,53 @@ async def _post_admin_mobile(ctx: dict, **kw: Any) -> str:
|
||||
)
|
||||
|
||||
|
||||
register_layout(Layout("root", _root_full, _root_oob, _root_mobile))
|
||||
register_layout(Layout("post", _post_full, _post_oob, _post_mobile))
|
||||
register_layout(Layout("post-admin", _post_admin_full, _post_admin_oob, _post_admin_mobile))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# register_sx_layout — declarative layout from .sx defcomp names
|
||||
# ---------------------------------------------------------------------------
|
||||
# (defined below, used immediately after for built-in "root" layout)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def register_sx_layout(name: str, full_defcomp: str, oob_defcomp: str,
|
||||
mobile_defcomp: str | None = None) -> None:
|
||||
"""Register a layout that delegates entirely to .sx defcomps.
|
||||
|
||||
The defcomps read ctx values as free variables from the evaluation
|
||||
environment (populated by ``_ctx_to_env``). Python layouts become
|
||||
one-liners::
|
||||
|
||||
register_sx_layout("account", "account-layout-full",
|
||||
"account-layout-oob", "account-layout-mobile")
|
||||
"""
|
||||
from .helpers import render_to_sx_with_env, _ctx_to_env
|
||||
|
||||
async def full_fn(ctx: dict, **kw: Any) -> str:
|
||||
env = _ctx_to_env(ctx)
|
||||
env.update({k.replace("_", "-"): v for k, v in kw.items()})
|
||||
return await render_to_sx_with_env(full_defcomp, env)
|
||||
|
||||
async def oob_fn(ctx: dict, **kw: Any) -> str:
|
||||
env = _ctx_to_env(ctx, oob=True)
|
||||
env.update({k.replace("_", "-"): v for k, v in kw.items()})
|
||||
return await render_to_sx_with_env(oob_defcomp, env)
|
||||
|
||||
mobile_fn = None
|
||||
if mobile_defcomp:
|
||||
async def mobile_fn(ctx: dict, **kw: Any) -> str:
|
||||
env = _ctx_to_env(ctx)
|
||||
env.update({k.replace("_", "-"): v for k, v in kw.items()})
|
||||
return await render_to_sx_with_env(mobile_defcomp, env)
|
||||
|
||||
register_layout(Layout(name, full_fn, oob_fn, mobile_fn))
|
||||
|
||||
|
||||
# Register built-in "root" layout via .sx defcomps
|
||||
register_sx_layout("root", "layout-root-full", "layout-root-oob", "layout-root-mobile")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Callable layout — services register custom Python layout functions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -146,6 +146,33 @@
|
||||
(when auth-menu
|
||||
(div :class "p-3 border-t border-stone-200" auth-menu))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Root header/mobile wrappers — read ctx values from env free variables
|
||||
;; Used by register_sx_layout so .sx defcomps compose without Python
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~root-header (&key oob)
|
||||
(~header-row-sx :cart-mini cart-mini :blog-url blog-url :site-title site-title
|
||||
:app-label app-label :nav-tree nav-tree :auth-menu auth-menu
|
||||
:nav-panel nav-panel :settings-url settings-url :is-admin is-admin
|
||||
:oob oob))
|
||||
|
||||
(defcomp ~root-mobile ()
|
||||
(~mobile-root-nav :nav-tree nav-tree :auth-menu auth-menu))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Built-in layout defcomps — used by register_sx_layout("root", ...)
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~layout-root-full ()
|
||||
(~root-header))
|
||||
|
||||
(defcomp ~layout-root-oob ()
|
||||
(~oob-header-sx :parent-id "root-header-child" :row (~root-header)))
|
||||
|
||||
(defcomp ~layout-root-mobile ()
|
||||
(~root-mobile))
|
||||
|
||||
(defcomp ~error-content (&key errnum message image)
|
||||
(div :class "text-center p-8 max-w-lg mx-auto"
|
||||
(div :class "font-bold text-2xl md:text-4xl text-red-500 mb-4" errnum)
|
||||
|
||||
Reference in New Issue
Block a user