Move market composition from Python to .sx defcomps (Phase 3)
Python sxc/pages/ functions no longer build nested sx_call chains or reference leaf component names. Instead they extract data (URLs, prices, CSRF, cart state) and call a single top-level composition defcomp with pure data values. The .sx defcomps handle all component-to-component wiring, iteration (map), and conditional rendering. New .sx composition defcomps: - headers.sx: ~market-header-from-data, ~market-desktop-nav-from-data, ~market-product-header-from-data, ~market-product-admin-header-from-data - prices.sx: ~market-prices-header-from-data, ~market-card-price-from-data - navigation.sx: ~market-mobile-nav-from-data - cards.sx: ~market-product-cards-content, ~market-card-from-data, ~market-cards-content, ~market-landing-from-data - detail.sx: ~market-product-detail-from-data, ~market-detail-gallery-from-data, ~market-detail-info-from-data - meta.sx: ~market-product-meta-from-data - filters.sx: ~market-desktop-filter-from-data, ~market-mobile-chips-from-data, ~market-mobile-filter-content-from-data, plus 6 sub-composition defcomps Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
"""Product/market card builders."""
|
||||
"""Product/market card data builders."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from shared.sx.parser import SxExpr
|
||||
from shared.sx.helpers import sx_call
|
||||
|
||||
from .utils import _set_prices, _price_str
|
||||
@@ -11,11 +10,11 @@ from .filters import _MOBILE_SENTINEL_HS, _DESKTOP_SENTINEL_HS
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Product card (browse grid item)
|
||||
# Product card data extraction
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _product_card_sx(p: dict, ctx: dict) -> str:
|
||||
"""Build a single product card for browse grid as sx call."""
|
||||
def _product_card_data(p: dict, ctx: dict) -> dict:
|
||||
"""Extract data for a single product card."""
|
||||
from quart import url_for
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
from shared.utils import route_prefix
|
||||
@@ -49,7 +48,8 @@ def _product_card_sx(p: dict, ctx: dict) -> str:
|
||||
if raw_stickers and callable(asset_url_fn):
|
||||
for s in raw_stickers:
|
||||
ring = " ring-2 ring-emerald-500 rounded" if s in selected_stickers else ""
|
||||
sticker_data.append({"src": asset_url_fn(f"stickers/{s}.svg"), "name": s, "ring-cls": ring})
|
||||
sticker_data.append({"src": asset_url_fn(f"stickers/{s}.svg"),
|
||||
"name": s, "ring-cls": ring})
|
||||
|
||||
# Title highlighting
|
||||
title = p.get("title", "")
|
||||
@@ -71,38 +71,37 @@ def _product_card_sx(p: dict, ctx: dict) -> str:
|
||||
brand = p.get("brand", "")
|
||||
brand_highlight = " bg-yellow-200" if brand in selected_brands else ""
|
||||
|
||||
kwargs = dict(
|
||||
href=item_href, hx_select=hx_select, slug=slug,
|
||||
image=p.get("image", ""), brand=brand, brand_highlight=brand_highlight,
|
||||
special_price=sp_str, regular_price=rp_str,
|
||||
cart_action=cart_action, quantity=quantity, cart_href=cart_href, csrf=csrf,
|
||||
title=title,
|
||||
has_like=bool(user),
|
||||
)
|
||||
d: dict[str, Any] = {
|
||||
"href": item_href, "hx-select": hx_select, "slug": slug,
|
||||
"image": p.get("image", ""), "brand": brand, "brand-highlight": brand_highlight,
|
||||
"special-price": sp_str, "regular-price": rp_str,
|
||||
"cart-action": cart_action, "quantity": quantity, "cart-href": cart_href, "csrf": csrf,
|
||||
"title": title, "has-like": bool(user),
|
||||
}
|
||||
|
||||
if label_srcs:
|
||||
kwargs["labels"] = label_srcs
|
||||
d["labels"] = label_srcs
|
||||
elif labels:
|
||||
kwargs["labels"] = labels
|
||||
d["labels"] = labels
|
||||
|
||||
if user:
|
||||
kwargs["liked"] = p.get("is_liked", False)
|
||||
kwargs["like_action"] = url_for("market.browse.product.like_toggle", product_slug=slug)
|
||||
d["liked"] = p.get("is_liked", False)
|
||||
d["like-action"] = url_for("market.browse.product.like_toggle", product_slug=slug)
|
||||
|
||||
if sticker_data:
|
||||
kwargs["stickers"] = sticker_data
|
||||
d["stickers"] = sticker_data
|
||||
|
||||
if has_highlight:
|
||||
kwargs["has_highlight"] = True
|
||||
kwargs["search_pre"] = search_pre
|
||||
kwargs["search_mid"] = search_mid
|
||||
kwargs["search_post"] = search_post
|
||||
d["has-highlight"] = True
|
||||
d["search-pre"] = search_pre
|
||||
d["search-mid"] = search_mid
|
||||
d["search-post"] = search_post
|
||||
|
||||
return sx_call("market-product-card", **kwargs)
|
||||
return d
|
||||
|
||||
|
||||
def _product_cards_sx(ctx: dict) -> str:
|
||||
"""S-expression wire format for product cards (client renders)."""
|
||||
"""S-expression wire format for product cards — delegates to .sx defcomp."""
|
||||
from shared.utils import route_prefix
|
||||
|
||||
prefix = route_prefix()
|
||||
@@ -112,48 +111,46 @@ def _product_cards_sx(ctx: dict) -> str:
|
||||
current_local_href = ctx.get("current_local_href", "/")
|
||||
qs_fn = ctx.get("qs_filter")
|
||||
|
||||
parts = []
|
||||
for p in products:
|
||||
parts.append(_product_card_sx(p, ctx))
|
||||
product_data = [_product_card_data(p, ctx) for p in products]
|
||||
|
||||
next_url = ""
|
||||
if page < total_pages:
|
||||
if callable(qs_fn):
|
||||
next_qs = qs_fn({"page": page + 1})
|
||||
else:
|
||||
next_qs = f"?page={page + 1}"
|
||||
next_url = prefix + current_local_href + next_qs
|
||||
parts.append(sx_call("sentinel-mobile",
|
||||
id=f"sentinel-{page}-m", next_url=next_url,
|
||||
hyperscript=_MOBILE_SENTINEL_HS))
|
||||
parts.append(sx_call("sentinel-desktop",
|
||||
id=f"sentinel-{page}-d", next_url=next_url,
|
||||
hyperscript=_DESKTOP_SENTINEL_HS))
|
||||
else:
|
||||
parts.append(sx_call("end-of-results"))
|
||||
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
return sx_call("market-product-cards-content",
|
||||
products=product_data,
|
||||
page=page,
|
||||
total_pages=total_pages,
|
||||
next_url=next_url,
|
||||
mobile_sentinel_hs=_MOBILE_SENTINEL_HS,
|
||||
desktop_sentinel_hs=_DESKTOP_SENTINEL_HS)
|
||||
|
||||
|
||||
def _like_button_sx(slug: str, liked: bool, csrf: str, ctx: dict) -> str:
|
||||
"""Build the like/unlike heart button overlay as sx."""
|
||||
def _like_button_data(slug: str, liked: bool, csrf: str, ctx: dict) -> dict:
|
||||
"""Extract like button data."""
|
||||
from quart import url_for
|
||||
|
||||
action = url_for("market.browse.product.like_toggle", product_slug=slug)
|
||||
icon_cls = "fa-solid fa-heart text-red-500" if liked else "fa-regular fa-heart text-stone-400"
|
||||
return sx_call(
|
||||
"market-like-button",
|
||||
form_id=f"like-{slug}", action=action, slug=slug,
|
||||
csrf=csrf, icon_cls=icon_cls,
|
||||
)
|
||||
return {
|
||||
"form-id": f"like-{slug}",
|
||||
"action": action,
|
||||
"slug": slug,
|
||||
"csrf": csrf,
|
||||
"icon-cls": icon_cls,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Market cards (all markets / page markets)
|
||||
# Market cards data extraction
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool = True,
|
||||
post_slug: str = "") -> str:
|
||||
"""Build a single market card as sx."""
|
||||
def _market_card_data(market: Any, page_info: dict, *, show_page_badge: bool = True,
|
||||
post_slug: str = "") -> dict:
|
||||
"""Extract data for a single market card."""
|
||||
from shared.infrastructure.urls import market_url
|
||||
|
||||
name = getattr(market, "name", "")
|
||||
@@ -161,78 +158,60 @@ def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool = Tru
|
||||
slug = getattr(market, "slug", "")
|
||||
container_id = getattr(market, "container_id", None)
|
||||
|
||||
href = ""
|
||||
badge_href = ""
|
||||
badge_title = ""
|
||||
|
||||
if show_page_badge and page_info:
|
||||
pi = page_info.get(container_id, {})
|
||||
p_slug = pi.get("slug", "")
|
||||
p_title = pi.get("title", "")
|
||||
market_href = market_url(f"/{p_slug}/{slug}/") if p_slug else ""
|
||||
href = market_url(f"/{p_slug}/{slug}/") if p_slug else ""
|
||||
if p_title:
|
||||
badge_href = market_url(f"/{p_slug}/")
|
||||
badge_title = p_title
|
||||
else:
|
||||
p_slug = post_slug
|
||||
p_title = ""
|
||||
market_href = market_url(f"/{post_slug}/{slug}/") if post_slug else ""
|
||||
href = market_url(f"/{post_slug}/{slug}/") if post_slug else ""
|
||||
|
||||
title_sx = ""
|
||||
if market_href:
|
||||
title_sx = sx_call("market-market-card-title-link", href=market_href, name=name)
|
||||
else:
|
||||
title_sx = sx_call("market-market-card-title", name=name)
|
||||
|
||||
desc_sx = ""
|
||||
if description:
|
||||
desc_sx = sx_call("market-market-card-desc", description=description)
|
||||
|
||||
badge_sx = ""
|
||||
if show_page_badge and p_title:
|
||||
badge_href = market_url(f"/{p_slug}/")
|
||||
badge_sx = sx_call("market-market-card-badge", href=badge_href, title=p_title)
|
||||
|
||||
return sx_call(
|
||||
"market-market-card",
|
||||
title_content=title_sx or None,
|
||||
desc_content=desc_sx or None,
|
||||
badge_content=badge_sx or None,
|
||||
)
|
||||
return {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"href": href,
|
||||
"show-badge": show_page_badge,
|
||||
"badge-href": badge_href,
|
||||
"badge-title": badge_title,
|
||||
}
|
||||
|
||||
|
||||
def _market_cards_sx(markets: list, page_info: dict, page: int, has_more: bool,
|
||||
next_url: str, *, show_page_badge: bool = True,
|
||||
post_slug: str = "") -> str:
|
||||
"""Build market cards with infinite scroll sentinel as sx."""
|
||||
parts = []
|
||||
for m in markets:
|
||||
parts.append(_market_card_sx(m, page_info, show_page_badge=show_page_badge,
|
||||
post_slug=post_slug))
|
||||
if has_more:
|
||||
parts.append(sx_call(
|
||||
"sentinel-simple",
|
||||
id=f"sentinel-{page}", next_url=next_url,
|
||||
))
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
next_url: str, *, show_page_badge: bool = True,
|
||||
post_slug: str = "") -> str:
|
||||
"""Build market cards as sx — delegates to .sx defcomp."""
|
||||
market_data = [_market_card_data(m, page_info, show_page_badge=show_page_badge,
|
||||
post_slug=post_slug) for m in markets]
|
||||
return sx_call("market-cards-content",
|
||||
markets=market_data,
|
||||
page=page,
|
||||
has_more=has_more,
|
||||
next_url=next_url)
|
||||
|
||||
|
||||
def _markets_grid(cards_sx: str) -> str:
|
||||
"""Wrap market cards in a grid as sx."""
|
||||
from shared.sx.parser import SxExpr
|
||||
return sx_call("market-markets-grid", cards=SxExpr(cards_sx))
|
||||
|
||||
|
||||
def _no_markets_sx(message: str = "No markets available") -> str:
|
||||
"""Empty state for markets as sx."""
|
||||
return sx_call("empty-state", icon="fa fa-store", message=message,
|
||||
cls="px-3 py-12 text-center text-stone-400")
|
||||
cls="px-3 py-12 text-center text-stone-400")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Market landing page
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _market_landing_content_sx(post: dict) -> str:
|
||||
"""Build market landing page content as sx."""
|
||||
parts: list[str] = []
|
||||
if post.get("custom_excerpt"):
|
||||
parts.append(sx_call("market-landing-excerpt", text=post["custom_excerpt"]))
|
||||
if post.get("feature_image"):
|
||||
parts.append(sx_call("market-landing-image", src=post["feature_image"]))
|
||||
if post.get("html"):
|
||||
parts.append(sx_call("market-landing-html", html=post["html"]))
|
||||
inner = "(<> " + " ".join(parts) + ")" if parts else "(<>)"
|
||||
return sx_call("market-landing-content", inner=SxExpr(inner))
|
||||
"""Build market landing page content — delegates to .sx defcomp."""
|
||||
return sx_call("market-landing-from-data",
|
||||
excerpt=post.get("custom_excerpt") or None,
|
||||
feature_image=post.get("feature_image") or None,
|
||||
html=post.get("html") or None)
|
||||
|
||||
Reference in New Issue
Block a user