Merge branch 'worktree-macros-essays' into macros
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m11s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m11s
This commit is contained in:
50
blog/sx/layouts.sx
Normal file
50
blog/sx/layouts.sx
Normal file
@@ -0,0 +1,50 @@
|
||||
;; Blog layout defcomps — root header from env free variables,
|
||||
;; blog-specific headers passed as &key params.
|
||||
|
||||
;; --- Blog layout (root + invisible blog header) ---
|
||||
|
||||
(defcomp ~blog-layout-full (&key blog-header)
|
||||
(<> (~root-header :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)
|
||||
blog-header))
|
||||
|
||||
;; --- Settings layout (root + settings header) ---
|
||||
|
||||
(defcomp ~settings-layout-full (&key settings-header)
|
||||
(<> (~root-header :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)
|
||||
settings-header))
|
||||
|
||||
;; --- Sub-settings layout (root + settings + sub row) ---
|
||||
|
||||
(defcomp ~sub-settings-layout-full (&key settings-header sub-header)
|
||||
(<> (~root-header :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)
|
||||
settings-header sub-header))
|
||||
|
||||
(defcomp ~sub-settings-layout-oob (&key settings-header-oob sub-header-oob)
|
||||
(<> settings-header-oob sub-header-oob))
|
||||
|
||||
;; --- Settings nav links (replaces Python _settings_nav_sx loop) ---
|
||||
|
||||
(defcomp ~blog-settings-nav (&key select-colours)
|
||||
(let* ((links (list
|
||||
(dict :endpoint "menu_items.defpage_menu_items_page" :icon "fa fa-bars" :label "Menu Items")
|
||||
(dict :endpoint "snippets.defpage_snippets_page" :icon "fa fa-puzzle-piece" :label "Snippets")
|
||||
(dict :endpoint "blog.tag_groups_admin.defpage_tag_groups_page" :icon "fa fa-tags" :label "Tag Groups")
|
||||
(dict :endpoint "settings.defpage_cache_page" :icon "fa fa-refresh" :label "Cache"))))
|
||||
(<> (map (lambda (lnk)
|
||||
(~nav-link
|
||||
:href (url-for (get lnk "endpoint"))
|
||||
:icon (get lnk "icon")
|
||||
:label (get lnk "label")
|
||||
:select-colours (or select-colours "")))
|
||||
links))))
|
||||
|
||||
;; --- Editor panel wrapper ---
|
||||
|
||||
(defcomp ~blog-editor-panel (&key parts)
|
||||
(<> parts))
|
||||
@@ -144,21 +144,8 @@ async def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
||||
|
||||
async def _settings_nav_sx(ctx: dict) -> str:
|
||||
from shared.sx.helpers import render_to_sx
|
||||
from quart import url_for as qurl
|
||||
|
||||
select_colours = ctx.get("select_colours", "")
|
||||
parts = []
|
||||
for endpoint, icon, label in [
|
||||
("menu_items.defpage_menu_items_page", "bars", "Menu Items"),
|
||||
("snippets.defpage_snippets_page", "puzzle-piece", "Snippets"),
|
||||
("blog.tag_groups_admin.defpage_tag_groups_page", "tags", "Tag Groups"),
|
||||
("settings.defpage_cache_page", "refresh", "Cache"),
|
||||
]:
|
||||
href = qurl(endpoint)
|
||||
parts.append(await render_to_sx("nav-link",
|
||||
href=href, icon=f"fa fa-{icon}", label=label,
|
||||
select_colours=select_colours))
|
||||
return "(<> " + " ".join(parts) + ")" if parts else ""
|
||||
return await render_to_sx("blog-settings-nav",
|
||||
select_colours=ctx.get("select_colours", ""))
|
||||
|
||||
|
||||
async def _sub_settings_header_sx(row_id: str, child_id: str, href: str,
|
||||
@@ -197,34 +184,34 @@ def _register_blog_layouts() -> None:
|
||||
# --- Blog layout (root + blog header) ---
|
||||
|
||||
async def _blog_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
blog_hdr = await _blog_header_sx(ctx)
|
||||
return "(<> " + root_hdr + " " + blog_hdr + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
from shared.sx.parser import SxExpr
|
||||
return await render_to_sx_with_env("blog-layout-full", _ctx_to_env(ctx),
|
||||
blog_header=SxExpr(await _blog_header_sx(ctx)))
|
||||
|
||||
|
||||
async def _blog_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, oob_header_sx
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
blog_hdr = await _blog_header_sx(ctx)
|
||||
rows = "(<> " + root_hdr + " " + blog_hdr + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, oob_header_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
rows = await render_to_sx_with_env("blog-layout-full", _ctx_to_env(ctx),
|
||||
blog_header=SxExpr(await _blog_header_sx(ctx)))
|
||||
return await oob_header_sx("root-header-child", "blog-header-child", rows)
|
||||
|
||||
|
||||
# --- Settings layout (root + settings header) ---
|
||||
|
||||
async def _settings_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
settings_hdr = await _settings_header_sx(ctx)
|
||||
return "(<> " + root_hdr + " " + settings_hdr + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
from shared.sx.parser import SxExpr
|
||||
return await render_to_sx_with_env("settings-layout-full", _ctx_to_env(ctx),
|
||||
settings_header=SxExpr(await _settings_header_sx(ctx)))
|
||||
|
||||
|
||||
async def _settings_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, oob_header_sx
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
settings_hdr = await _settings_header_sx(ctx)
|
||||
rows = "(<> " + root_hdr + " " + settings_hdr + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, oob_header_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
rows = await render_to_sx_with_env("settings-layout-full", _ctx_to_env(ctx),
|
||||
settings_header=SxExpr(await _settings_header_sx(ctx)))
|
||||
return await oob_header_sx("root-header-child", "root-settings-header-child", rows)
|
||||
|
||||
|
||||
@@ -236,24 +223,27 @@ async def _settings_mobile(ctx: dict, **kw: Any) -> str:
|
||||
|
||||
async def _sub_settings_full(ctx: dict, row_id: str, child_id: str,
|
||||
endpoint: str, icon: str, label: str) -> str:
|
||||
from shared.sx.helpers import root_header_sx
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
from shared.sx.parser import SxExpr
|
||||
from quart import url_for as qurl
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
settings_hdr = await _settings_header_sx(ctx)
|
||||
sub_hdr = await _sub_settings_header_sx(row_id, child_id,
|
||||
qurl(endpoint), icon, label, ctx)
|
||||
return "(<> " + root_hdr + " " + settings_hdr + " " + sub_hdr + ")"
|
||||
return await render_to_sx_with_env("sub-settings-layout-full", _ctx_to_env(ctx),
|
||||
settings_header=SxExpr(await _settings_header_sx(ctx)),
|
||||
sub_header=SxExpr(await _sub_settings_header_sx(
|
||||
row_id, child_id, qurl(endpoint), icon, label, ctx)))
|
||||
|
||||
|
||||
async def _sub_settings_oob(ctx: dict, row_id: str, child_id: str,
|
||||
endpoint: str, icon: str, label: str) -> str:
|
||||
from shared.sx.helpers import oob_header_sx
|
||||
from shared.sx.helpers import oob_header_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
from quart import url_for as qurl
|
||||
settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
|
||||
sub_hdr = await _sub_settings_header_sx(row_id, child_id,
|
||||
qurl(endpoint), icon, label, ctx)
|
||||
sub_hdr = await _sub_settings_header_sx(
|
||||
row_id, child_id, qurl(endpoint), icon, label, ctx)
|
||||
sub_oob = await oob_header_sx("root-settings-header-child", child_id, sub_hdr)
|
||||
return "(<> " + settings_hdr_oob + " " + sub_oob + ")"
|
||||
return await render_to_sx("sub-settings-layout-oob",
|
||||
settings_header_oob=SxExpr(settings_hdr_oob),
|
||||
sub_header_oob=SxExpr(sub_oob))
|
||||
|
||||
|
||||
# --- Cache ---
|
||||
@@ -308,26 +298,31 @@ async def _tag_groups_oob(ctx: dict, **kw: Any) -> str:
|
||||
|
||||
async def _tag_group_edit_full(ctx: dict, **kw: Any) -> str:
|
||||
from quart import request, url_for as qurl
|
||||
from shared.sx.helpers import root_header_sx
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
from shared.sx.parser import SxExpr
|
||||
g_id = (request.view_args or {}).get("id")
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
settings_hdr = await _settings_header_sx(ctx)
|
||||
sub_hdr = await _sub_settings_header_sx("tag-groups-row", "tag-groups-header-child",
|
||||
qurl("defpage_tag_group_edit", id=g_id),
|
||||
"tags", "Tag Groups", ctx)
|
||||
return "(<> " + root_hdr + " " + settings_hdr + " " + sub_hdr + ")"
|
||||
return await render_to_sx_with_env("sub-settings-layout-full", _ctx_to_env(ctx),
|
||||
settings_header=SxExpr(await _settings_header_sx(ctx)),
|
||||
sub_header=SxExpr(await _sub_settings_header_sx(
|
||||
"tag-groups-row", "tag-groups-header-child",
|
||||
qurl("defpage_tag_group_edit", id=g_id),
|
||||
"tags", "Tag Groups", ctx)))
|
||||
|
||||
|
||||
async def _tag_group_edit_oob(ctx: dict, **kw: Any) -> str:
|
||||
from quart import request, url_for as qurl
|
||||
from shared.sx.helpers import oob_header_sx
|
||||
from shared.sx.helpers import oob_header_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
g_id = (request.view_args or {}).get("id")
|
||||
settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
|
||||
sub_hdr = await _sub_settings_header_sx("tag-groups-row", "tag-groups-header-child",
|
||||
qurl("defpage_tag_group_edit", id=g_id),
|
||||
"tags", "Tag Groups", ctx)
|
||||
sub_hdr = await _sub_settings_header_sx(
|
||||
"tag-groups-row", "tag-groups-header-child",
|
||||
qurl("defpage_tag_group_edit", id=g_id),
|
||||
"tags", "Tag Groups", ctx)
|
||||
sub_oob = await oob_header_sx("root-settings-header-child", "tag-groups-header-child", sub_hdr)
|
||||
return "(<> " + settings_hdr_oob + " " + sub_oob + ")"
|
||||
return await render_to_sx("sub-settings-layout-oob",
|
||||
settings_header_oob=SxExpr(settings_hdr_oob),
|
||||
sub_header_oob=SxExpr(sub_oob))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -512,7 +507,9 @@ async def render_editor_panel(save_error: str | None = None, is_page: bool = Fal
|
||||
sx_editor_js_src=sx_editor_js,
|
||||
init_js=init_js))
|
||||
|
||||
return "(<> " + " ".join(parts) + ")" if parts else ""
|
||||
from shared.sx.parser import SxExpr
|
||||
return await render_to_sx("blog-editor-panel",
|
||||
parts=SxExpr("(<> " + " ".join(parts) + ")")) if parts else ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1116,6 +1113,7 @@ async def _h_post_edit_content(slug=None, **kw):
|
||||
sx_editor_js_src=sx_editor_js,
|
||||
init_js=init_js))
|
||||
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
return await render_to_sx("blog-editor-panel",
|
||||
parts=SxExpr("(<> " + " ".join(parts) + ")"))
|
||||
|
||||
|
||||
|
||||
78
cart/sx/layouts.sx
Normal file
78
cart/sx/layouts.sx
Normal file
@@ -0,0 +1,78 @@
|
||||
;; Cart layout defcomps — root header from env free variables,
|
||||
;; cart-specific headers passed as &key params.
|
||||
|
||||
;; --- cart-page layout: root + cart row + page-cart row ---
|
||||
|
||||
(defcomp ~cart-page-layout-full (&key cart-row page-cart-row)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx
|
||||
:inner (<> cart-row
|
||||
(~header-child-sx :id "cart-header-child" :inner page-cart-row)))))
|
||||
|
||||
(defcomp ~cart-page-layout-oob (&key root-header-oob cart-row-oob page-cart-row)
|
||||
(<> (~oob-header-sx :parent-id "cart-header-child" :row page-cart-row)
|
||||
cart-row-oob
|
||||
root-header-oob))
|
||||
|
||||
;; --- cart-admin layout: root + post header + admin header ---
|
||||
|
||||
(defcomp ~cart-admin-layout-full (&key post-header admin-header)
|
||||
(<> (~root-header :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)
|
||||
post-header admin-header))
|
||||
|
||||
;; --- orders-within-cart: root + auth-simple + orders ---
|
||||
|
||||
(defcomp ~cart-orders-layout-full (&key list-url)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx
|
||||
:inner (<> (~auth-header-row-simple :account-url account-url)
|
||||
(~header-child-sx :id "auth-header-child"
|
||||
:inner (~orders-header-row :list-url list-url))))))
|
||||
|
||||
(defcomp ~cart-orders-layout-oob (&key list-url)
|
||||
(<> (~auth-header-row-simple :account-url account-url :oob true)
|
||||
(~oob-header-sx
|
||||
:parent-id "auth-header-child"
|
||||
:row (~orders-header-row :list-url list-url))
|
||||
(~root-header :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 true)))
|
||||
|
||||
;; --- order-detail-within-cart: root + auth-simple + orders + order ---
|
||||
|
||||
(defcomp ~cart-order-detail-layout-full (&key list-url detail-url order-label)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx
|
||||
:inner (<> (~auth-header-row-simple :account-url account-url)
|
||||
(~header-child-sx :id "auth-header-child"
|
||||
:inner (<> (~orders-header-row :list-url list-url)
|
||||
(~header-child-sx :id "orders-header-child"
|
||||
:inner (~menu-row-sx :id "order-row" :level 3 :colour "sky"
|
||||
:link-href detail-url
|
||||
:link-label order-label
|
||||
:icon "fa fa-gbp"))))))))
|
||||
|
||||
(defcomp ~cart-order-detail-layout-oob (&key detail-url order-label)
|
||||
(<> (~oob-header-sx
|
||||
:parent-id "orders-header-child"
|
||||
:row (~menu-row-sx :id "order-row" :level 3 :colour "sky"
|
||||
:link-href detail-url :link-label order-label
|
||||
:icon "fa fa-gbp" :oob true))
|
||||
(~root-header :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 true)))
|
||||
|
||||
;; --- orders rows wrapper (for infinite scroll) ---
|
||||
|
||||
(defcomp ~cart-orders-rows (&key rows next-scroll)
|
||||
(<> rows next-scroll))
|
||||
@@ -20,7 +20,7 @@ def _load_cart_page_files() -> None:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Header helpers (moved from sx_components.py)
|
||||
# Header helpers (still needed by layouts and render functions)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _ensure_post_ctx(ctx: dict, page_post: Any) -> dict:
|
||||
@@ -94,30 +94,8 @@ async def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False)
|
||||
)
|
||||
|
||||
|
||||
async def _auth_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
||||
from shared.sx.helpers import render_to_sx, call_url
|
||||
return await render_to_sx(
|
||||
"auth-header-row-simple",
|
||||
account_url=call_url(ctx, "account_url", ""),
|
||||
oob=oob,
|
||||
)
|
||||
|
||||
|
||||
async def _orders_header_sx(ctx: dict, list_url: str) -> str:
|
||||
from shared.sx.helpers import render_to_sx
|
||||
return await render_to_sx("orders-header-row", list_url=list_url)
|
||||
|
||||
|
||||
async def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
|
||||
selected: str = "") -> str:
|
||||
from shared.sx.helpers import post_admin_header_sx
|
||||
slug = page_post.slug if page_post else ""
|
||||
ctx = _ensure_post_ctx(ctx, page_post)
|
||||
return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Order serialization helpers (used by route render functions below)
|
||||
# Order serialization helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _serialize_order(order: Any) -> dict:
|
||||
@@ -157,11 +135,11 @@ def _serialize_calendar_entry(e: Any) -> dict:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Render functions (called by routes)
|
||||
# Render functions (called by routes) — delegate header composition to .sx
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def render_orders_page(ctx, orders, page, total_pages, search, search_count, url_for_fn, qs_fn):
|
||||
from shared.sx.helpers import render_to_sx, root_header_sx, search_desktop_sx, search_mobile_sx, full_page_sx
|
||||
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, search_desktop_sx, search_mobile_sx, full_page_sx
|
||||
from shared.utils import route_prefix
|
||||
ctx["search"] = search
|
||||
ctx["search_count"] = search_count
|
||||
@@ -171,12 +149,9 @@ async def render_orders_page(ctx, orders, page, total_pages, search, search_coun
|
||||
order_dicts = [_serialize_order(o) for o in orders]
|
||||
content = await render_to_sx("orders-list-content", orders=order_dicts,
|
||||
page=page, total_pages=total_pages, rows_url=list_url, detail_url_prefix=detail_url_prefix)
|
||||
hdr = await root_header_sx(ctx)
|
||||
auth = await _auth_header_sx(ctx)
|
||||
orders_hdr = await _orders_header_sx(ctx, list_url)
|
||||
auth_child_inner = await render_to_sx("header-child-sx", id="auth-header-child", inner=SxExpr(orders_hdr))
|
||||
auth_child = await render_to_sx("header-child-sx", inner=SxExpr("(<> " + auth + " " + auth_child_inner + ")"))
|
||||
header_rows = "(<> " + hdr + " " + auth_child + ")"
|
||||
header_rows = await render_to_sx_with_env("cart-orders-layout-full", _ctx_to_env(ctx),
|
||||
list_url=list_url,
|
||||
)
|
||||
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
|
||||
return await full_page_sx(ctx, header_rows=header_rows, filter=filt,
|
||||
aside=await search_desktop_sx(ctx), content=content)
|
||||
@@ -192,17 +167,21 @@ async def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
|
||||
parts = []
|
||||
for od in order_dicts:
|
||||
parts.append(await render_to_sx("order-row-pair", order=od, detail_url_prefix=detail_url_prefix))
|
||||
next_scroll = ""
|
||||
if page < total_pages:
|
||||
next_url = list_url + qs_fn(page=page + 1)
|
||||
parts.append(await render_to_sx("infinite-scroll", url=next_url, page=page,
|
||||
total_pages=total_pages, id_prefix="orders", colspan=5))
|
||||
next_scroll = await render_to_sx("infinite-scroll", url=next_url, page=page,
|
||||
total_pages=total_pages, id_prefix="orders", colspan=5)
|
||||
else:
|
||||
parts.append(await render_to_sx("order-end-row"))
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
next_scroll = await render_to_sx("order-end-row")
|
||||
return await render_to_sx("cart-orders-rows",
|
||||
rows=SxExpr("(<> " + " ".join(parts) + ")"),
|
||||
next_scroll=SxExpr(next_scroll),
|
||||
)
|
||||
|
||||
|
||||
async def render_orders_oob(ctx, orders, page, total_pages, search, search_count, url_for_fn, qs_fn):
|
||||
from shared.sx.helpers import render_to_sx, root_header_sx, search_desktop_sx, search_mobile_sx, oob_page_sx
|
||||
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, search_desktop_sx, search_mobile_sx, oob_page_sx
|
||||
from shared.utils import route_prefix
|
||||
ctx["search"] = search
|
||||
ctx["search_count"] = search_count
|
||||
@@ -212,17 +191,15 @@ async def render_orders_oob(ctx, orders, page, total_pages, search, search_count
|
||||
order_dicts = [_serialize_order(o) for o in orders]
|
||||
content = await render_to_sx("orders-list-content", orders=order_dicts,
|
||||
page=page, total_pages=total_pages, rows_url=list_url, detail_url_prefix=detail_url_prefix)
|
||||
auth_oob = await _auth_header_sx(ctx, oob=True)
|
||||
orders_hdr = await _orders_header_sx(ctx, list_url)
|
||||
auth_child_oob = await render_to_sx("oob-header-sx", parent_id="auth-header-child", row=SxExpr(orders_hdr))
|
||||
root_oob = await root_header_sx(ctx, oob=True)
|
||||
oobs = "(<> " + auth_oob + " " + auth_child_oob + " " + root_oob + ")"
|
||||
oobs = await render_to_sx_with_env("cart-orders-layout-oob", _ctx_to_env(ctx, oob=True),
|
||||
list_url=list_url,
|
||||
)
|
||||
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
|
||||
return await oob_page_sx(oobs=oobs, filter=filt, aside=await search_desktop_sx(ctx), content=content)
|
||||
|
||||
|
||||
async def render_order_page(ctx, order, calendar_entries, url_for_fn):
|
||||
from shared.sx.helpers import render_to_sx, root_header_sx, full_page_sx
|
||||
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, full_page_sx
|
||||
from shared.utils import route_prefix
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
pfx = route_prefix()
|
||||
@@ -235,20 +212,15 @@ async def render_order_page(ctx, order, calendar_entries, url_for_fn):
|
||||
main = await render_to_sx("order-detail-content", order=order_data, calendar_entries=cal_data)
|
||||
filt = await render_to_sx("order-detail-filter-content", order=order_data,
|
||||
list_url=list_url, recheck_url=recheck_url, pay_url=pay_url, csrf=generate_csrf_token())
|
||||
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=f"Order {order.id}", icon="fa fa-gbp")
|
||||
auth = await _auth_header_sx(ctx)
|
||||
orders_hdr = await _orders_header_sx(ctx, list_url)
|
||||
orders_child = await render_to_sx("header-child-sx", id="orders-header-child", inner=SxExpr(order_row))
|
||||
auth_inner = "(<> " + orders_hdr + " " + orders_child + ")"
|
||||
auth_child = await render_to_sx("header-child-sx", id="auth-header-child", inner=SxExpr(auth_inner))
|
||||
order_child = await render_to_sx("header-child-sx", inner=SxExpr("(<> " + auth + " " + auth_child + ")"))
|
||||
return await full_page_sx(ctx, header_rows="(<> " + hdr + " " + order_child + ")", filter=filt, content=main)
|
||||
header_rows = await render_to_sx_with_env("cart-order-detail-layout-full", _ctx_to_env(ctx),
|
||||
list_url=list_url, detail_url=detail_url,
|
||||
order_label=f"Order {order.id}",
|
||||
)
|
||||
return await full_page_sx(ctx, header_rows=header_rows, filter=filt, content=main)
|
||||
|
||||
|
||||
async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
|
||||
from shared.sx.helpers import render_to_sx, root_header_sx, oob_page_sx
|
||||
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, oob_page_sx
|
||||
from shared.utils import route_prefix
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
pfx = route_prefix()
|
||||
@@ -261,19 +233,19 @@ async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
|
||||
main = await render_to_sx("order-detail-content", order=order_data, calendar_entries=cal_data)
|
||||
filt = await render_to_sx("order-detail-filter-content", order=order_data,
|
||||
list_url=list_url, recheck_url=recheck_url, pay_url=pay_url, csrf=generate_csrf_token())
|
||||
order_row_oob = await render_to_sx("menu-row-sx", id="order-row", level=3, colour="sky",
|
||||
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp", oob=True)
|
||||
orders_child_oob = await render_to_sx("oob-header-sx", parent_id="orders-header-child", row=SxExpr(order_row_oob))
|
||||
root_oob = await root_header_sx(ctx, oob=True)
|
||||
return await oob_page_sx(oobs="(<> " + orders_child_oob + " " + root_oob + ")", filter=filt, content=main)
|
||||
oobs = await render_to_sx_with_env("cart-order-detail-layout-oob", _ctx_to_env(ctx, oob=True),
|
||||
detail_url=detail_url,
|
||||
order_label=f"Order {order.id}",
|
||||
)
|
||||
return await oob_page_sx(oobs=oobs, filter=filt, content=main)
|
||||
|
||||
|
||||
async def render_checkout_error_page(ctx, error=None, order=None):
|
||||
from shared.sx.helpers import render_to_sx, root_header_sx, full_page_sx
|
||||
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, _ctx_to_env, full_page_sx
|
||||
from shared.infrastructure.urls import cart_url
|
||||
err_msg = error or "Unexpected error while creating the hosted checkout session."
|
||||
order_sx = await render_to_sx("checkout-error-order-id", oid=f"#{order.id}") if order else None
|
||||
hdr = await root_header_sx(ctx)
|
||||
hdr = await render_to_sx_with_env("layout-root-full", _ctx_to_env(ctx))
|
||||
filt = await render_to_sx("checkout-error-header")
|
||||
content = await render_to_sx("checkout-error-content", msg=err_msg,
|
||||
order=SxExpr(order_sx) if order_sx else None, back_url=cart_url("/"))
|
||||
@@ -294,7 +266,7 @@ async def render_cart_payments_panel(ctx):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Layouts
|
||||
# Layouts — thin wrappers delegating to .sx defcomps in cart/sx/layouts.sx
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_cart_layouts() -> None:
|
||||
@@ -304,50 +276,46 @@ def _register_cart_layouts() -> None:
|
||||
|
||||
|
||||
async def _cart_page_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
page_post = ctx.get("page_post")
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
child = await _cart_header_sx(ctx)
|
||||
page_hdr = await _page_cart_header_sx(ctx, page_post)
|
||||
inner_child = await render_to_sx("header-child-sx", id="cart-header-child", inner=SxExpr(page_hdr))
|
||||
nested = await render_to_sx(
|
||||
"header-child-sx",
|
||||
inner=SxExpr("(<> " + child + " " + inner_child + ")"),
|
||||
env = _ctx_to_env(ctx)
|
||||
return await render_to_sx_with_env("cart-page-layout-full", env,
|
||||
cart_row=SxExpr(await _cart_header_sx(ctx)),
|
||||
page_cart_row=SxExpr(await _page_cart_header_sx(ctx, page_post)),
|
||||
)
|
||||
return "(<> " + root_hdr + " " + nested + ")"
|
||||
|
||||
|
||||
async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx, render_to_sx
|
||||
from shared.sx.parser import SxExpr
|
||||
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env, root_header_sx
|
||||
page_post = ctx.get("page_post")
|
||||
page_hdr = await _page_cart_header_sx(ctx, page_post)
|
||||
child_oob = await render_to_sx("oob-header-sx",
|
||||
parent_id="cart-header-child",
|
||||
row=SxExpr(page_hdr))
|
||||
cart_hdr_oob = await _cart_header_sx(ctx, oob=True)
|
||||
root_hdr_oob = await root_header_sx(ctx, oob=True)
|
||||
return "(<> " + child_oob + " " + cart_hdr_oob + " " + root_hdr_oob + ")"
|
||||
env = _ctx_to_env(ctx, oob=True)
|
||||
return await render_to_sx_with_env("cart-page-layout-oob", env,
|
||||
root_header_oob=SxExpr(await root_header_sx(ctx, oob=True)),
|
||||
cart_row_oob=SxExpr(await _cart_header_sx(ctx, oob=True)),
|
||||
page_cart_row=SxExpr(await _page_cart_header_sx(ctx, page_post)),
|
||||
)
|
||||
|
||||
|
||||
async def _cart_admin_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import root_header_sx
|
||||
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
page_post = ctx.get("page_post")
|
||||
selected = kw.get("selected", "")
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
post_hdr = await _post_header_sx(ctx, page_post)
|
||||
admin_hdr = await _cart_page_admin_header_sx(ctx, page_post, selected=selected)
|
||||
return "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
|
||||
env = _ctx_to_env(ctx)
|
||||
return await render_to_sx_with_env("cart-admin-layout-full", env,
|
||||
post_header=SxExpr(await _post_header_sx(ctx, page_post)),
|
||||
admin_header=SxExpr(await _cart_page_admin_header_sx(ctx, page_post, selected=selected)),
|
||||
)
|
||||
|
||||
|
||||
async def _cart_admin_oob(ctx: dict, **kw: Any) -> str:
|
||||
|
||||
page_post = ctx.get("page_post")
|
||||
selected = kw.get("selected", "")
|
||||
return await _cart_page_admin_header_sx(ctx, page_post, oob=True, selected=selected)
|
||||
|
||||
|
||||
async def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
|
||||
selected: str = "") -> str:
|
||||
from shared.sx.helpers import post_admin_header_sx
|
||||
slug = page_post.slug if page_post else ""
|
||||
ctx = _ensure_post_ctx(ctx, page_post)
|
||||
return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
|
||||
|
||||
50
market/sx/layouts.sx
Normal file
50
market/sx/layouts.sx
Normal file
@@ -0,0 +1,50 @@
|
||||
;; Market layout defcomps — root header from env free variables,
|
||||
;; market-specific headers passed as &key params.
|
||||
|
||||
;; --- Browse layout: root + post header + market header ---
|
||||
|
||||
(defcomp ~market-browse-layout-full (&key post-header market-header)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx :inner (<> post-header market-header))))
|
||||
|
||||
(defcomp ~market-browse-layout-oob (&key oob-header post-header-oob clear-oob)
|
||||
(<> oob-header post-header-oob clear-oob))
|
||||
|
||||
;; --- Product layout: root + post + market + product ---
|
||||
|
||||
(defcomp ~market-product-layout-full (&key post-header market-header product-header)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx :inner (<> post-header market-header product-header))))
|
||||
|
||||
;; --- Product admin layout: root + post + market + product + admin ---
|
||||
|
||||
(defcomp ~market-product-admin-layout-full (&key post-header market-header product-header admin-header)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx :inner (<> post-header market-header product-header admin-header))))
|
||||
|
||||
;; --- Market admin layout: root + post + market + market-admin ---
|
||||
|
||||
(defcomp ~market-admin-layout-full (&key post-header market-header admin-header)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx :inner (<> post-header market-header admin-header))))
|
||||
|
||||
(defcomp ~market-admin-layout-oob (&key market-header-oob admin-oob-header clear-oob)
|
||||
(<> market-header-oob admin-oob-header clear-oob))
|
||||
|
||||
;; --- OOB wrappers ---
|
||||
|
||||
(defcomp ~market-oob-wrap (&key parts)
|
||||
(<> parts))
|
||||
|
||||
;; --- Content wrappers ---
|
||||
|
||||
(defcomp ~market-content-padded (&key content)
|
||||
(<> content (div :class "pb-8")))
|
||||
@@ -6,7 +6,6 @@ from typing import Any
|
||||
from shared.sx.parser import serialize, SxExpr
|
||||
from shared.sx.helpers import (
|
||||
render_to_sx,
|
||||
root_header_sx,
|
||||
post_header_sx as _post_header_sx,
|
||||
post_admin_header_sx,
|
||||
oob_header_sx as _oob_header_sx,
|
||||
@@ -1227,9 +1226,10 @@ async def render_browse_page(ctx: dict) -> str:
|
||||
cards = await _product_cards_sx(ctx)
|
||||
content = await _product_grid(cards)
|
||||
|
||||
hdr = await root_header_sx(ctx)
|
||||
child = "(<> " + await _post_header_sx(ctx) + " " + await _market_header_sx(ctx) + ")"
|
||||
hdr = "(<> " + hdr + " " + await header_child_sx(child) + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
hdr = await render_to_sx_with_env("market-browse-layout-full", _ctx_to_env(ctx),
|
||||
post_header=SxExpr(await _post_header_sx(ctx)),
|
||||
market_header=SxExpr(await _market_header_sx(ctx)))
|
||||
menu = await _mobile_nav_panel_sx(ctx)
|
||||
filter_sx = await _mobile_filter_summary_sx(ctx)
|
||||
aside_sx = await _desktop_filter_sx(ctx)
|
||||
@@ -1243,12 +1243,13 @@ async def render_browse_oob(ctx: dict) -> str:
|
||||
cards = await _product_cards_sx(ctx)
|
||||
content = await _product_grid(cards)
|
||||
|
||||
oobs = await _oob_header_sx("post-header-child", "market-header-child",
|
||||
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
|
||||
await _market_header_sx(ctx))
|
||||
post_hdr = await _post_header_sx(ctx, oob=True)
|
||||
oobs = "(<> " + oobs + " " + post_hdr + " "
|
||||
oobs += _clear_deeper_oob("post-row", "post-header-child",
|
||||
"market-row", "market-header-child") + ")"
|
||||
oobs = await render_to_sx("market-browse-layout-oob",
|
||||
oob_header=SxExpr(oob_hdr),
|
||||
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
|
||||
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
|
||||
"market-row", "market-header-child")))
|
||||
menu = await _mobile_nav_panel_sx(ctx)
|
||||
filter_sx = await _mobile_filter_summary_sx(ctx)
|
||||
aside_sx = await _desktop_filter_sx(ctx)
|
||||
@@ -1271,11 +1272,11 @@ async def render_product_page(ctx: dict, d: dict) -> str:
|
||||
content = await _product_detail_sx(d, ctx)
|
||||
meta = await _product_meta_sx(d, ctx)
|
||||
|
||||
hdr = await root_header_sx(ctx)
|
||||
post_hdr = await _post_header_sx(ctx)
|
||||
child = "(<> " + post_hdr + " " + await _market_header_sx(ctx) + " " + await _product_header_sx(ctx, d) + ")"
|
||||
hdr_child = await header_child_sx(child)
|
||||
hdr = "(<> " + hdr + " " + hdr_child + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
hdr = await render_to_sx_with_env("market-product-layout-full", _ctx_to_env(ctx),
|
||||
post_header=SxExpr(await _post_header_sx(ctx)),
|
||||
market_header=SxExpr(await _market_header_sx(ctx)),
|
||||
product_header=SxExpr(await _product_header_sx(ctx, d)))
|
||||
return await full_page_sx(ctx, header_rows=hdr, content=content, meta=meta)
|
||||
|
||||
|
||||
@@ -1283,13 +1284,13 @@ async def render_product_oob(ctx: dict, d: dict) -> str:
|
||||
"""OOB response: product detail."""
|
||||
content = await _product_detail_sx(d, ctx)
|
||||
|
||||
oobs = "(<> " + await _market_header_sx(ctx, oob=True) + " "
|
||||
oob_hdr = await _oob_header_sx("market-header-child", "product-header-child",
|
||||
await _product_header_sx(ctx, d))
|
||||
oobs += oob_hdr + " "
|
||||
oobs += _clear_deeper_oob("post-row", "post-header-child",
|
||||
oobs = await render_to_sx("market-oob-wrap",
|
||||
parts=SxExpr("(<> " + await _market_header_sx(ctx, oob=True) + " "
|
||||
+ await _oob_header_sx("market-header-child", "product-header-child",
|
||||
await _product_header_sx(ctx, d)) + " "
|
||||
+ _clear_deeper_oob("post-row", "post-header-child",
|
||||
"market-row", "market-header-child",
|
||||
"product-row", "product-header-child") + ")"
|
||||
"product-row", "product-header-child") + ")"))
|
||||
menu = await _mobile_nav_panel_sx(ctx)
|
||||
return await oob_page_sx(oobs=oobs, content=content, menu=menu)
|
||||
|
||||
@@ -1302,12 +1303,12 @@ async def render_product_admin_page(ctx: dict, d: dict) -> str:
|
||||
"""Full page: product admin."""
|
||||
content = await _product_detail_sx(d, ctx)
|
||||
|
||||
hdr = await root_header_sx(ctx)
|
||||
post_hdr = await _post_header_sx(ctx)
|
||||
child = "(<> " + post_hdr + " " + await _market_header_sx(ctx)
|
||||
child += " " + await _product_header_sx(ctx, d) + " " + await _product_admin_header_sx(ctx, d) + ")"
|
||||
hdr_child = await header_child_sx(child)
|
||||
hdr = "(<> " + hdr + " " + hdr_child + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
hdr = await render_to_sx_with_env("market-product-admin-layout-full", _ctx_to_env(ctx),
|
||||
post_header=SxExpr(await _post_header_sx(ctx)),
|
||||
market_header=SxExpr(await _market_header_sx(ctx)),
|
||||
product_header=SxExpr(await _product_header_sx(ctx, d)),
|
||||
admin_header=SxExpr(await _product_admin_header_sx(ctx, d)))
|
||||
return await full_page_sx(ctx, header_rows=hdr, content=content)
|
||||
|
||||
|
||||
@@ -1315,14 +1316,14 @@ async def render_product_admin_oob(ctx: dict, d: dict) -> str:
|
||||
"""OOB response: product admin."""
|
||||
content = await _product_detail_sx(d, ctx)
|
||||
|
||||
oobs = "(<> " + await _product_header_sx(ctx, d, oob=True) + " "
|
||||
oob_hdr = await _oob_header_sx("product-header-child", "product-admin-header-child",
|
||||
await _product_admin_header_sx(ctx, d))
|
||||
oobs += oob_hdr + " "
|
||||
oobs += _clear_deeper_oob("post-row", "post-header-child",
|
||||
oobs = await render_to_sx("market-oob-wrap",
|
||||
parts=SxExpr("(<> " + await _product_header_sx(ctx, d, oob=True) + " "
|
||||
+ await _oob_header_sx("product-header-child", "product-admin-header-child",
|
||||
await _product_admin_header_sx(ctx, d)) + " "
|
||||
+ _clear_deeper_oob("post-row", "post-header-child",
|
||||
"market-row", "market-header-child",
|
||||
"product-row", "product-header-child",
|
||||
"product-admin-row", "product-admin-header-child") + ")"
|
||||
"product-admin-row", "product-admin-header-child") + ")"))
|
||||
return await oob_page_sx(oobs=oobs, content=content)
|
||||
|
||||
|
||||
@@ -1536,18 +1537,20 @@ def _register_market_layouts() -> None:
|
||||
|
||||
|
||||
async def _market_full(ctx: dict, **kw: Any) -> str:
|
||||
hdr = await root_header_sx(ctx)
|
||||
child = "(<> " + await _post_header_sx(ctx) + " " + await _market_header_sx(ctx) + ")"
|
||||
return "(<> " + hdr + " " + await header_child_sx(child) + ")"
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
return await render_to_sx_with_env("market-browse-layout-full", _ctx_to_env(ctx),
|
||||
post_header=SxExpr(await _post_header_sx(ctx)),
|
||||
market_header=SxExpr(await _market_header_sx(ctx)))
|
||||
|
||||
|
||||
async def _market_oob(ctx: dict, **kw: Any) -> str:
|
||||
oobs = await _oob_header_sx("post-header-child", "market-header-child",
|
||||
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
|
||||
await _market_header_sx(ctx))
|
||||
oobs = "(<> " + oobs + " " + await _post_header_sx(ctx, oob=True) + " "
|
||||
oobs += _clear_deeper_oob("post-row", "post-header-child",
|
||||
"market-row", "market-header-child") + ")"
|
||||
return oobs
|
||||
return await render_to_sx("market-browse-layout-oob",
|
||||
oob_header=SxExpr(oob_hdr),
|
||||
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
|
||||
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
|
||||
"market-row", "market-header-child")))
|
||||
|
||||
|
||||
async def _market_mobile(ctx: dict, **kw: Any) -> str:
|
||||
@@ -1555,22 +1558,23 @@ async def _market_mobile(ctx: dict, **kw: Any) -> str:
|
||||
|
||||
|
||||
async def _market_admin_full(ctx: dict, **kw: Any) -> str:
|
||||
from shared.sx.helpers import render_to_sx_with_env, _ctx_to_env
|
||||
selected = kw.get("selected", "")
|
||||
hdr = await root_header_sx(ctx)
|
||||
child = "(<> " + await _post_header_sx(ctx) + " " + await _market_header_sx(ctx) + " "
|
||||
child += await _market_admin_header_sx(ctx, selected=selected) + ")"
|
||||
return "(<> " + hdr + " " + await header_child_sx(child) + ")"
|
||||
return await render_to_sx_with_env("market-admin-layout-full", _ctx_to_env(ctx),
|
||||
post_header=SxExpr(await _post_header_sx(ctx)),
|
||||
market_header=SxExpr(await _market_header_sx(ctx)),
|
||||
admin_header=SxExpr(await _market_admin_header_sx(ctx, selected=selected)))
|
||||
|
||||
|
||||
async def _market_admin_oob(ctx: dict, **kw: Any) -> str:
|
||||
selected = kw.get("selected", "")
|
||||
oobs = "(<> " + await _market_header_sx(ctx, oob=True) + " "
|
||||
oobs += await _oob_header_sx("market-header-child", "market-admin-header-child",
|
||||
await _market_admin_header_sx(ctx, selected=selected)) + " "
|
||||
oobs += _clear_deeper_oob("post-row", "post-header-child",
|
||||
return await render_to_sx("market-admin-layout-oob",
|
||||
market_header_oob=SxExpr(await _market_header_sx(ctx, oob=True)),
|
||||
admin_oob_header=SxExpr(await _oob_header_sx("market-header-child", "market-admin-header-child",
|
||||
await _market_admin_header_sx(ctx, selected=selected))),
|
||||
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
|
||||
"market-row", "market-header-child",
|
||||
"market-admin-row", "market-admin-header-child") + ")"
|
||||
return oobs
|
||||
"market-admin-row", "market-admin-header-child")))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
|
||||
104
test/sx/components.sx
Normal file
104
test/sx/components.sx
Normal file
@@ -0,0 +1,104 @@
|
||||
;; Test service composition defcomps — replaces Python string concatenation
|
||||
;; in test/sxc/pages/__init__.py.
|
||||
|
||||
;; Service filter nav links
|
||||
(defcomp ~test-service-nav (&key services active-service)
|
||||
(<>
|
||||
(~nav-link :href "/" :label "all"
|
||||
:is-selected (if (not active-service) "true" nil)
|
||||
:select-colours "aria-selected:bg-sky-200 aria-selected:text-sky-900")
|
||||
(map (lambda (svc)
|
||||
(~nav-link :href (str "/?service=" svc) :label svc
|
||||
:is-selected (if (= active-service svc) "true" nil)
|
||||
:select-colours "aria-selected:bg-sky-200 aria-selected:text-sky-900"))
|
||||
services)))
|
||||
|
||||
;; Test header menu row
|
||||
(defcomp ~test-header-row (&key services active-service)
|
||||
(~menu-row-sx :id "test-row" :level 1 :colour "sky"
|
||||
:link-href "/" :link-label "Tests" :icon "fa fa-flask"
|
||||
:nav (~test-service-nav :services services :active-service active-service)
|
||||
:child-id "test-header-child"))
|
||||
|
||||
;; Layout: full page header stack (reads root header values from env free variables)
|
||||
(defcomp ~test-layout-full (&key services active-service)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx
|
||||
:inner (~test-header-row :services services :active-service active-service))))
|
||||
|
||||
;; Map test dicts to test-row components
|
||||
(defcomp ~test-rows (&key tests)
|
||||
(<> (map (lambda (t)
|
||||
(~test-row
|
||||
:nodeid (get t "nodeid")
|
||||
:outcome (get t "outcome")
|
||||
:duration (str (get t "duration"))
|
||||
:longrepr (or (get t "longrepr") "")))
|
||||
tests)))
|
||||
|
||||
;; Grouped test rows with service headers
|
||||
(defcomp ~test-grouped-rows (&key sections)
|
||||
(<> (map (lambda (sec)
|
||||
(<> (~test-service-header
|
||||
:service (get sec "service")
|
||||
:total (str (get sec "total"))
|
||||
:passed (str (get sec "passed"))
|
||||
:failed (str (get sec "failed")))
|
||||
(~test-rows :tests (get sec "tests"))))
|
||||
sections)))
|
||||
|
||||
;; Results partial: conditional rendering based on running/result state
|
||||
(defcomp ~test-results-partial (&key status summary-data tests sections has-failures)
|
||||
(let* ((state (get summary-data "state")))
|
||||
(<>
|
||||
(~test-summary
|
||||
:status (get summary-data "status")
|
||||
:passed (get summary-data "passed")
|
||||
:failed (get summary-data "failed")
|
||||
:errors (get summary-data "errors")
|
||||
:skipped (get summary-data "skipped")
|
||||
:total (get summary-data "total")
|
||||
:duration (get summary-data "duration")
|
||||
:last-run (get summary-data "last_run")
|
||||
:running (get summary-data "running")
|
||||
:csrf (get summary-data "csrf")
|
||||
:active-filter (get summary-data "active_filter"))
|
||||
(cond
|
||||
((= state "running") (~test-running-indicator))
|
||||
((= state "no-results") (~test-no-results))
|
||||
((= state "empty-filtered") (~test-no-results))
|
||||
(true (~test-results-table
|
||||
:rows (~test-grouped-rows :sections sections)
|
||||
:has-failures has-failures))))))
|
||||
|
||||
;; Wrap results in a div with optional HTMX polling
|
||||
(defcomp ~test-results-wrap (&key running inner)
|
||||
(div :id "test-results" :class "space-y-6 p-4"
|
||||
:sx-get (when running "/results")
|
||||
:sx-trigger (when running "every 2s")
|
||||
:sx-swap (when running "outerHTML")
|
||||
inner))
|
||||
|
||||
;; Test detail section wrapper
|
||||
(defcomp ~test-detail-section (&key test)
|
||||
(section :id "main-panel"
|
||||
:class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"
|
||||
(~test-detail
|
||||
:nodeid (get test "nodeid")
|
||||
:outcome (get test "outcome")
|
||||
:duration (str (get test "duration"))
|
||||
:longrepr (or (get test "longrepr") ""))))
|
||||
|
||||
;; Detail page header stack (reads root header values from env free variables)
|
||||
(defcomp ~test-detail-layout-full (&key services test-nodeid test-label)
|
||||
(<> (~root-header :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)
|
||||
(~header-child-sx
|
||||
:inner (<> (~test-header-row :services services)
|
||||
(~header-child-sx :id "test-header-child"
|
||||
:inner (~menu-row-sx :id "test-detail-row" :level 2 :colour "sky"
|
||||
:link-href (str "/test/" test-nodeid)
|
||||
:link-label test-label))))))
|
||||
@@ -5,10 +5,7 @@ import os
|
||||
from datetime import datetime
|
||||
|
||||
from shared.sx.jinja_bridge import load_service_components
|
||||
from shared.sx.helpers import (
|
||||
render_to_sx, SxExpr,
|
||||
root_header_sx, full_page_sx, header_child_sx,
|
||||
)
|
||||
from shared.sx.helpers import render_to_sx, SxExpr, render_to_sx_with_env, _ctx_to_env, full_page_sx
|
||||
|
||||
# Load test-specific .sx components at import time
|
||||
load_service_components(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||
@@ -21,10 +18,6 @@ def _format_time(ts: float | None) -> str:
|
||||
return datetime.fromtimestamp(ts).strftime("%-d %b %Y, %H:%M:%S")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Menu / header
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_FILTER_MAP = {
|
||||
"passed": "passed",
|
||||
"failed": "failed",
|
||||
@@ -46,120 +39,27 @@ def _filter_tests(tests: list[dict], active_filter: str | None,
|
||||
return filtered
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Results partial
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def test_detail_sx(test: dict) -> str:
|
||||
"""Return s-expression wire format for a test detail view."""
|
||||
inner = await render_to_sx(
|
||||
"test-detail",
|
||||
nodeid=test["nodeid"],
|
||||
outcome=test["outcome"],
|
||||
duration=str(test["duration"]),
|
||||
longrepr=test.get("longrepr", ""),
|
||||
)
|
||||
return (
|
||||
f'(section :id "main-panel"'
|
||||
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
|
||||
f' {inner})'
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sx-native versions — return sx source (not HTML)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _test_header_sx(ctx: dict, active_service: str | None = None) -> str:
|
||||
"""Build the Tests menu-row as sx call."""
|
||||
nav = await _service_nav_sx(ctx, active_service)
|
||||
return await render_to_sx("menu-row-sx",
|
||||
id="test-row", level=1, colour="sky",
|
||||
link_href="/", link_label="Tests", icon="fa fa-flask",
|
||||
nav=SxExpr(nav),
|
||||
child_id="test-header-child",
|
||||
)
|
||||
|
||||
|
||||
async def _service_nav_sx(ctx: dict, active_service: str | None = None) -> str:
|
||||
"""Service filter nav as sx."""
|
||||
def _service_list() -> list[str]:
|
||||
from runner import _SERVICE_ORDER
|
||||
parts = []
|
||||
parts.append(await render_to_sx("nav-link",
|
||||
href="/", label="all",
|
||||
is_selected="true" if not active_service else None,
|
||||
select_colours="aria-selected:bg-sky-200 aria-selected:text-sky-900",
|
||||
))
|
||||
for svc in _SERVICE_ORDER:
|
||||
parts.append(await render_to_sx("nav-link",
|
||||
href=f"/?service={svc}", label=svc,
|
||||
is_selected="true" if active_service == svc else None,
|
||||
select_colours="aria-selected:bg-sky-200 aria-selected:text-sky-900",
|
||||
))
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
return list(_SERVICE_ORDER)
|
||||
|
||||
|
||||
async def _header_stack_sx(ctx: dict, active_service: str | None = None) -> str:
|
||||
"""Full header stack as sx."""
|
||||
hdr = await root_header_sx(ctx)
|
||||
inner = await _test_header_sx(ctx, active_service)
|
||||
child = await header_child_sx(inner)
|
||||
return "(<> " + hdr + " " + child + ")"
|
||||
|
||||
|
||||
async def _test_rows_sx(tests: list[dict]) -> str:
|
||||
"""Render all test result rows as sx."""
|
||||
parts = []
|
||||
for t in tests:
|
||||
parts.append(await render_to_sx("test-row",
|
||||
nodeid=t["nodeid"],
|
||||
outcome=t["outcome"],
|
||||
duration=str(t["duration"]),
|
||||
longrepr=t.get("longrepr", ""),
|
||||
))
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
|
||||
|
||||
async def _grouped_rows_sx(tests: list[dict]) -> str:
|
||||
"""Test rows grouped by service as sx."""
|
||||
from runner import group_tests_by_service
|
||||
sections = group_tests_by_service(tests)
|
||||
parts = []
|
||||
for sec in sections:
|
||||
parts.append(await render_to_sx("test-service-header",
|
||||
service=sec["service"],
|
||||
total=str(sec["total"]),
|
||||
passed=str(sec["passed"]),
|
||||
failed=str(sec["failed"]),
|
||||
))
|
||||
parts.append(await _test_rows_sx(sec["tests"]))
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
|
||||
|
||||
async def _results_partial_sx(result: dict | None, running: bool, csrf: str,
|
||||
active_filter: str | None = None,
|
||||
active_service: str | None = None) -> str:
|
||||
"""Results section as sx."""
|
||||
def _build_summary_data(result: dict | None, running: bool, csrf: str,
|
||||
active_filter: str | None) -> dict:
|
||||
"""Prepare summary data dict for the ~test-results-partial defcomp."""
|
||||
if running and not result:
|
||||
summary = await render_to_sx("test-summary",
|
||||
status="running", passed="0", failed="0", errors="0",
|
||||
skipped="0", total="0", duration="...",
|
||||
last_run="in progress", running=True, csrf=csrf,
|
||||
active_filter=active_filter,
|
||||
)
|
||||
return "(<> " + summary + " " + await render_to_sx("test-running-indicator") + ")"
|
||||
|
||||
return dict(state="running", status="running", passed="0", failed="0",
|
||||
errors="0", skipped="0", total="0", duration="...",
|
||||
last_run="in progress", running=True, csrf=csrf,
|
||||
active_filter=active_filter)
|
||||
if not result:
|
||||
summary = await render_to_sx("test-summary",
|
||||
status=None, passed="0", failed="0", errors="0",
|
||||
skipped="0", total="0", duration="0",
|
||||
last_run="never", running=running, csrf=csrf,
|
||||
active_filter=active_filter,
|
||||
)
|
||||
return "(<> " + summary + " " + await render_to_sx("test-no-results") + ")"
|
||||
|
||||
return dict(state="no-results", status=None, passed="0", failed="0",
|
||||
errors="0", skipped="0", total="0", duration="0",
|
||||
last_run="never", running=running, csrf=csrf,
|
||||
active_filter=active_filter)
|
||||
status = "running" if running else result["status"]
|
||||
summary = await render_to_sx("test-summary",
|
||||
return dict(
|
||||
state="running" if running else "has-results",
|
||||
status=status,
|
||||
passed=str(result["passed"]),
|
||||
failed=str(result["failed"]),
|
||||
@@ -168,34 +68,14 @@ async def _results_partial_sx(result: dict | None, running: bool, csrf: str,
|
||||
total=str(result["total"]),
|
||||
duration=str(result["duration"]),
|
||||
last_run=_format_time(result["finished_at"]) if not running else "in progress",
|
||||
running=running,
|
||||
csrf=csrf,
|
||||
running=running, csrf=csrf,
|
||||
active_filter=active_filter,
|
||||
)
|
||||
|
||||
if running:
|
||||
return "(<> " + summary + " " + await render_to_sx("test-running-indicator") + ")"
|
||||
|
||||
tests = result.get("tests", [])
|
||||
tests = _filter_tests(tests, active_filter, active_service)
|
||||
if not tests:
|
||||
return "(<> " + summary + " " + await render_to_sx("test-no-results") + ")"
|
||||
|
||||
has_failures = result["failed"] > 0 or result["errors"] > 0
|
||||
rows = await _grouped_rows_sx(tests)
|
||||
table = await render_to_sx("test-results-table",
|
||||
rows=SxExpr(rows),
|
||||
has_failures=str(has_failures).lower(),
|
||||
)
|
||||
return "(<> " + summary + " " + table + ")"
|
||||
|
||||
|
||||
def _wrap_results_div_sx(inner: str, running: bool) -> str:
|
||||
"""Wrap results in a div with HTMX polling (sx)."""
|
||||
attrs = ':id "test-results" :class "space-y-6 p-4"'
|
||||
if running:
|
||||
attrs += ' :sx-get "/results" :sx-trigger "every 2s" :sx-swap "outerHTML"'
|
||||
return f'(div {attrs} {inner})'
|
||||
async def test_detail_sx(test: dict) -> str:
|
||||
"""Return s-expression wire format for a test detail view."""
|
||||
return await render_to_sx("test-detail-section", test=test)
|
||||
|
||||
|
||||
async def render_dashboard_page_sx(ctx: dict, result: dict | None,
|
||||
@@ -203,9 +83,26 @@ async def render_dashboard_page_sx(ctx: dict, result: dict | None,
|
||||
active_filter: str | None = None,
|
||||
active_service: str | None = None) -> str:
|
||||
"""Full page: test dashboard (sx wire format)."""
|
||||
hdr = await _header_stack_sx(ctx, active_service)
|
||||
inner = await _results_partial_sx(result, running, csrf, active_filter, active_service)
|
||||
content = _wrap_results_div_sx(inner, running)
|
||||
from runner import group_tests_by_service
|
||||
|
||||
summary_data = _build_summary_data(result, running, csrf, active_filter)
|
||||
sections = []
|
||||
has_failures = "false"
|
||||
if result and not running:
|
||||
tests = _filter_tests(result.get("tests", []), active_filter, active_service)
|
||||
if tests:
|
||||
sections = group_tests_by_service(tests)
|
||||
has_failures = str(result["failed"] > 0 or result["errors"] > 0).lower()
|
||||
else:
|
||||
summary_data["state"] = "empty-filtered"
|
||||
|
||||
inner = await render_to_sx("test-results-partial",
|
||||
summary_data=summary_data, sections=sections, has_failures=has_failures)
|
||||
content = await render_to_sx("test-results-wrap", running=running, inner=SxExpr(inner))
|
||||
hdr = await render_to_sx_with_env("test-layout-full", _ctx_to_env(ctx),
|
||||
services=_service_list(),
|
||||
active_service=active_service,
|
||||
)
|
||||
return await full_page_sx(ctx, header_rows=hdr, content=content)
|
||||
|
||||
|
||||
@@ -214,23 +111,31 @@ async def render_results_partial_sx(result: dict | None, running: bool,
|
||||
active_filter: str | None = None,
|
||||
active_service: str | None = None) -> str:
|
||||
"""HTMX partial: results section (sx wire format)."""
|
||||
inner = await _results_partial_sx(result, running, csrf, active_filter, active_service)
|
||||
return _wrap_results_div_sx(inner, running)
|
||||
from runner import group_tests_by_service
|
||||
|
||||
summary_data = _build_summary_data(result, running, csrf, active_filter)
|
||||
sections = []
|
||||
has_failures = "false"
|
||||
if result and not running:
|
||||
tests = _filter_tests(result.get("tests", []), active_filter, active_service)
|
||||
if tests:
|
||||
sections = group_tests_by_service(tests)
|
||||
has_failures = str(result["failed"] > 0 or result["errors"] > 0).lower()
|
||||
else:
|
||||
summary_data["state"] = "empty-filtered"
|
||||
|
||||
inner = await render_to_sx("test-results-partial",
|
||||
summary_data=summary_data, sections=sections, has_failures=has_failures)
|
||||
return await render_to_sx("test-results-wrap", running=running, inner=SxExpr(inner))
|
||||
|
||||
|
||||
async def render_test_detail_page_sx(ctx: dict, test: dict) -> str:
|
||||
"""Full page: test detail (sx wire format)."""
|
||||
root_hdr = await root_header_sx(ctx)
|
||||
test_row = await _test_header_sx(ctx)
|
||||
detail_row = await render_to_sx("menu-row-sx",
|
||||
id="test-detail-row", level=2, colour="sky",
|
||||
link_href=f"/test/{test['nodeid']}",
|
||||
link_label=test["nodeid"].rsplit("::", 1)[-1],
|
||||
hdr = await render_to_sx_with_env("test-detail-layout-full", _ctx_to_env(ctx),
|
||||
services=_service_list(),
|
||||
test_nodeid=test["nodeid"],
|
||||
test_label=test["nodeid"].rsplit("::", 1)[-1],
|
||||
)
|
||||
hdr_child_detail = await header_child_sx(detail_row, id="test-header-child")
|
||||
inner = "(<> " + test_row + " " + hdr_child_detail + ")"
|
||||
hdr_child_inner = await header_child_sx(inner)
|
||||
hdr = "(<> " + root_hdr + " " + hdr_child_inner + ")"
|
||||
content = await render_to_sx("test-detail",
|
||||
nodeid=test["nodeid"],
|
||||
outcome=test["outcome"],
|
||||
|
||||
Reference in New Issue
Block a user