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,6 @@
|
||||
"""Price helpers, OOB helpers, product detail/meta builders."""
|
||||
"""Price helpers, OOB helpers, product detail/meta data builders."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from shared.sx.parser import SxExpr
|
||||
from shared.sx.helpers import sx_call
|
||||
|
||||
|
||||
@@ -54,31 +51,14 @@ def _set_prices(item: dict) -> dict:
|
||||
rp_val=rp_val, rp_raw=rp_raw, rp_cur=rp_cur)
|
||||
|
||||
|
||||
def _card_price_sx(p: dict) -> str:
|
||||
"""Build price line for product card as sx call."""
|
||||
pr = _set_prices(p)
|
||||
sp_str = _price_str(pr["sp_val"], pr["sp_raw"], pr["sp_cur"])
|
||||
rp_str = _price_str(pr["rp_val"], pr["rp_raw"], pr["rp_cur"])
|
||||
parts: list[str] = []
|
||||
if pr["sp_val"]:
|
||||
parts.append(sx_call("market-price-special", price=sp_str))
|
||||
if pr["rp_val"]:
|
||||
parts.append(sx_call("market-price-regular-strike", price=rp_str))
|
||||
elif pr["rp_val"]:
|
||||
parts.append(sx_call("market-price-regular", price=rp_str))
|
||||
inner = "(<> " + " ".join(parts) + ")" if parts else None
|
||||
return sx_call("market-price-line", inner=SxExpr(inner) if inner else None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Product detail page content
|
||||
# Product detail data extraction
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _product_detail_sx(d: dict, ctx: dict) -> str:
|
||||
"""Build product detail main panel content as sx."""
|
||||
from quart import url_for
|
||||
def _product_detail_data(d: dict, ctx: dict) -> dict:
|
||||
"""Extract product detail page data for .sx composition."""
|
||||
from shared.browser.app.csrf import generate_csrf_token
|
||||
from .cards import _like_button_sx
|
||||
from .cards import _like_button_data
|
||||
|
||||
asset_url_fn = ctx.get("asset_url")
|
||||
user = ctx.get("user")
|
||||
@@ -91,132 +71,70 @@ def _product_detail_sx(d: dict, ctx: dict) -> str:
|
||||
brand = d.get("brand", "")
|
||||
slug = d.get("slug", "")
|
||||
|
||||
# Gallery
|
||||
if images:
|
||||
# Like button
|
||||
like_sx = ""
|
||||
if user:
|
||||
like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx)
|
||||
# Like button data
|
||||
like_data = None
|
||||
if user:
|
||||
like_data = _like_button_data(slug, liked_by_current_user, csrf, ctx)
|
||||
|
||||
# Main image + labels
|
||||
label_parts: list[str] = []
|
||||
if callable(asset_url_fn):
|
||||
for l in labels:
|
||||
label_parts.append(sx_call(
|
||||
"market-label-overlay",
|
||||
src=asset_url_fn("labels/" + l + ".svg"),
|
||||
))
|
||||
labels_sx = "(<> " + " ".join(label_parts) + ")" if label_parts else None
|
||||
# Label overlay URLs
|
||||
label_urls = []
|
||||
if callable(asset_url_fn):
|
||||
label_urls = [asset_url_fn("labels/" + l + ".svg") for l in labels]
|
||||
|
||||
gallery_inner = sx_call(
|
||||
"market-detail-gallery-inner",
|
||||
like=like_sx or None,
|
||||
image=images[0], alt=d.get("title", ""),
|
||||
labels=SxExpr(labels_sx) if labels_sx else None,
|
||||
brand=brand,
|
||||
)
|
||||
# Image data
|
||||
image_data = [{"src": u, "alt": d.get("title", "")} for u in images] if images else []
|
||||
|
||||
# Prev/next buttons
|
||||
nav_buttons = ""
|
||||
if len(images) > 1:
|
||||
nav_buttons = sx_call("market-detail-nav-buttons")
|
||||
# Thumbnail data
|
||||
thumb_data = []
|
||||
if len(images) > 1:
|
||||
for i, u in enumerate(images):
|
||||
thumb_data.append({"title": f"Image {i+1}", "src": u, "alt": f"thumb {i+1}"})
|
||||
|
||||
gallery_sx = sx_call(
|
||||
"market-detail-gallery",
|
||||
inner=gallery_inner,
|
||||
nav=nav_buttons or None,
|
||||
)
|
||||
|
||||
# Thumbnails
|
||||
gallery_parts = [gallery_sx]
|
||||
if len(images) > 1:
|
||||
thumb_parts = []
|
||||
for i, u in enumerate(images):
|
||||
thumb_parts.append(sx_call(
|
||||
"market-detail-thumb",
|
||||
title=f"Image {i+1}", src=u, alt=f"thumb {i+1}",
|
||||
))
|
||||
thumbs_sx = "(<> " + " ".join(thumb_parts) + ")"
|
||||
gallery_parts.append(sx_call("market-detail-thumbs", thumbs=SxExpr(thumbs_sx)))
|
||||
gallery_final = "(<> " + " ".join(gallery_parts) + ")"
|
||||
else:
|
||||
like_sx = ""
|
||||
if user:
|
||||
like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx)
|
||||
gallery_final = sx_call("market-detail-no-image",
|
||||
like=like_sx or None)
|
||||
|
||||
# Stickers below gallery
|
||||
stickers_sx = ""
|
||||
# Sticker items
|
||||
sticker_items = []
|
||||
if stickers and callable(asset_url_fn):
|
||||
sticker_parts = []
|
||||
for s in stickers:
|
||||
sticker_parts.append(sx_call(
|
||||
"market-detail-sticker",
|
||||
src=asset_url_fn("stickers/" + s + ".svg"), name=s,
|
||||
))
|
||||
sticker_items_sx = "(<> " + " ".join(sticker_parts) + ")"
|
||||
stickers_sx = sx_call("market-detail-stickers", items=SxExpr(sticker_items_sx))
|
||||
sticker_items.append({"src": asset_url_fn("stickers/" + s + ".svg"), "name": s})
|
||||
|
||||
# Right column: prices, description, sections
|
||||
pr = _set_prices(d)
|
||||
detail_parts: list[str] = []
|
||||
|
||||
# Unit price / case size extras
|
||||
extra_parts: list[str] = []
|
||||
# Extras (unit price, case size)
|
||||
extras = []
|
||||
ppu = d.get("price_per_unit") or d.get("price_per_unit_raw")
|
||||
if ppu:
|
||||
extra_parts.append(sx_call(
|
||||
"market-detail-unit-price",
|
||||
price=_price_str(d.get("price_per_unit"), d.get("price_per_unit_raw"), d.get("price_per_unit_currency")),
|
||||
))
|
||||
extras.append({
|
||||
"type": "unit-price",
|
||||
"value": _price_str(d.get("price_per_unit"), d.get("price_per_unit_raw"),
|
||||
d.get("price_per_unit_currency")),
|
||||
})
|
||||
if d.get("case_size_raw"):
|
||||
extra_parts.append(sx_call("market-detail-case-size", size=d["case_size_raw"]))
|
||||
if extra_parts:
|
||||
extras_sx = "(<> " + " ".join(extra_parts) + ")"
|
||||
detail_parts.append(sx_call("market-detail-extras", inner=SxExpr(extras_sx)))
|
||||
extras.append({"type": "case-size", "value": d["case_size_raw"]})
|
||||
|
||||
# Description
|
||||
desc_short = d.get("description_short")
|
||||
desc_html_val = d.get("description_html")
|
||||
if desc_short or desc_html_val:
|
||||
desc_parts: list[str] = []
|
||||
if desc_short:
|
||||
desc_parts.append(sx_call("market-detail-desc-short", text=desc_short))
|
||||
if desc_html_val:
|
||||
desc_parts.append(sx_call("market-detail-desc-html", html=desc_html_val))
|
||||
desc_inner = "(<> " + " ".join(desc_parts) + ")"
|
||||
detail_parts.append(sx_call("market-detail-desc-wrapper", inner=SxExpr(desc_inner)))
|
||||
return {
|
||||
"images": image_data or None,
|
||||
"labels": label_urls or None,
|
||||
"brand": brand,
|
||||
"like-data": like_data,
|
||||
"has-nav-buttons": len(images) > 1,
|
||||
"thumbs": thumb_data or None,
|
||||
"sticker-items": sticker_items or None,
|
||||
"extras": extras or None,
|
||||
"desc-short": d.get("description_short") or None,
|
||||
"desc-html": d.get("description_html") or None,
|
||||
"sections": d.get("sections") or None,
|
||||
}
|
||||
|
||||
# Sections (expandable)
|
||||
sections = d.get("sections", [])
|
||||
if sections:
|
||||
sec_parts = []
|
||||
for sec in sections:
|
||||
sec_parts.append(sx_call(
|
||||
"market-detail-section",
|
||||
title=sec.get("title", ""), html=sec.get("html", ""),
|
||||
))
|
||||
sec_items_sx = "(<> " + " ".join(sec_parts) + ")"
|
||||
detail_parts.append(sx_call("market-detail-sections", items=SxExpr(sec_items_sx)))
|
||||
|
||||
details_inner_sx = "(<> " + " ".join(detail_parts) + ")" if detail_parts else "(<>)"
|
||||
details_sx = sx_call("market-detail-right-col", inner=SxExpr(details_inner_sx))
|
||||
|
||||
return sx_call(
|
||||
"market-detail-layout",
|
||||
gallery=SxExpr(gallery_final),
|
||||
stickers=stickers_sx or None,
|
||||
details=details_sx,
|
||||
)
|
||||
def _product_detail_sx(d: dict, ctx: dict) -> str:
|
||||
"""Build product detail content — delegates to .sx defcomp."""
|
||||
data = _product_detail_data(d, ctx)
|
||||
return sx_call("market-product-detail-from-data", **data)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Product meta (OpenGraph, JSON-LD)
|
||||
# Product meta data extraction
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _product_meta_sx(d: dict, ctx: dict) -> str:
|
||||
"""Build product meta tags as sx (auto-hoisted to <head> by sx.js)."""
|
||||
def _product_meta_data(d: dict, ctx: dict) -> dict:
|
||||
"""Extract product meta/SEO data for .sx composition."""
|
||||
import json
|
||||
from quart import request
|
||||
|
||||
@@ -231,36 +149,8 @@ def _product_meta_sx(d: dict, ctx: dict) -> str:
|
||||
brand = d.get("brand", "")
|
||||
sku = d.get("sku", "")
|
||||
price = d.get("special_price") or d.get("regular_price") or d.get("rrp")
|
||||
price_currency = d.get("special_price_currency") or d.get("regular_price_currency") or d.get("rrp_currency")
|
||||
|
||||
parts = [sx_call("market-meta-title", title=title)]
|
||||
parts.append(sx_call("market-meta-description", description=description))
|
||||
if canonical:
|
||||
parts.append(sx_call("market-meta-canonical", href=canonical))
|
||||
|
||||
# OpenGraph
|
||||
site_title = ctx.get("base_title", "")
|
||||
parts.append(sx_call("market-meta-og", property="og:site_name", content=site_title))
|
||||
parts.append(sx_call("market-meta-og", property="og:type", content="product"))
|
||||
parts.append(sx_call("market-meta-og", property="og:title", content=title))
|
||||
parts.append(sx_call("market-meta-og", property="og:description", content=description))
|
||||
if canonical:
|
||||
parts.append(sx_call("market-meta-og", property="og:url", content=canonical))
|
||||
if image_url:
|
||||
parts.append(sx_call("market-meta-og", property="og:image", content=image_url))
|
||||
if price and price_currency:
|
||||
parts.append(sx_call("market-meta-og", property="product:price:amount", content=f"{price:.2f}"))
|
||||
parts.append(sx_call("market-meta-og", property="product:price:currency", content=price_currency))
|
||||
if brand:
|
||||
parts.append(sx_call("market-meta-og", property="product:brand", content=brand))
|
||||
|
||||
# Twitter
|
||||
card_type = "summary_large_image" if image_url else "summary"
|
||||
parts.append(sx_call("market-meta-twitter", name="twitter:card", content=card_type))
|
||||
parts.append(sx_call("market-meta-twitter", name="twitter:title", content=title))
|
||||
parts.append(sx_call("market-meta-twitter", name="twitter:description", content=description))
|
||||
if image_url:
|
||||
parts.append(sx_call("market-meta-twitter", name="twitter:image", content=image_url))
|
||||
price_currency = (d.get("special_price_currency") or d.get("regular_price_currency")
|
||||
or d.get("rrp_currency"))
|
||||
|
||||
# JSON-LD
|
||||
jsonld = {
|
||||
@@ -282,6 +172,21 @@ def _product_meta_sx(d: dict, ctx: dict) -> str:
|
||||
"url": canonical,
|
||||
"availability": "https://schema.org/InStock",
|
||||
}
|
||||
parts.append(sx_call("market-meta-jsonld", json=json.dumps(jsonld)))
|
||||
|
||||
return "(<> " + " ".join(parts) + ")"
|
||||
return {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"canonical": canonical or None,
|
||||
"image-url": image_url or None,
|
||||
"site-title": ctx.get("base_title", ""),
|
||||
"brand": brand or None,
|
||||
"price": f"{price:.2f}" if price and price_currency else None,
|
||||
"price-currency": price_currency if price else None,
|
||||
"jsonld-json": json.dumps(jsonld),
|
||||
}
|
||||
|
||||
|
||||
def _product_meta_sx(d: dict, ctx: dict) -> str:
|
||||
"""Build product meta tags — delegates to .sx defcomp."""
|
||||
data = _product_meta_data(d, ctx)
|
||||
return sx_call("market-product-meta-from-data", **data)
|
||||
|
||||
Reference in New Issue
Block a user