Add widget registry for universal UI decoupling
Introduces a widget system where domains register UI fragments into named slots (container_nav, container_card, account_page, account_link). Host apps iterate widgets generically without naming any domain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
22
services/widgets/__init__.py
Normal file
22
services/widgets/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Per-domain widget registration.
|
||||
|
||||
Called once at startup after domain services are registered.
|
||||
Only registers widgets for domains that are actually available.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def register_all_widgets() -> None:
|
||||
from shared.services.registry import services
|
||||
|
||||
if services.has("calendar"):
|
||||
from .calendar_widgets import register_calendar_widgets
|
||||
register_calendar_widgets()
|
||||
|
||||
if services.has("market"):
|
||||
from .market_widgets import register_market_widgets
|
||||
register_market_widgets()
|
||||
|
||||
if services.has("cart"):
|
||||
from .cart_widgets import register_cart_widgets
|
||||
register_cart_widgets()
|
||||
91
services/widgets/calendar_widgets.py
Normal file
91
services/widgets/calendar_widgets.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""Calendar-domain widgets: entries nav, calendar links, card entries, account pages."""
|
||||
from __future__ import annotations
|
||||
|
||||
from shared.contracts.widgets import NavWidget, CardWidget, AccountPageWidget
|
||||
from shared.services.widget_registry import widgets
|
||||
from shared.services.registry import services
|
||||
|
||||
|
||||
# -- container_nav: associated entries ----------------------------------------
|
||||
|
||||
async def _nav_entries_context(
|
||||
session, *, container_type, container_id, post_slug, page=1, **kw,
|
||||
):
|
||||
entries, has_more = await services.calendar.associated_entries(
|
||||
session, container_type, container_id, page,
|
||||
)
|
||||
return {
|
||||
"entries": entries,
|
||||
"has_more": has_more,
|
||||
"page": page,
|
||||
"post_slug": post_slug,
|
||||
}
|
||||
|
||||
|
||||
# -- container_nav: calendar links -------------------------------------------
|
||||
|
||||
async def _nav_calendars_context(
|
||||
session, *, container_type, container_id, post_slug, **kw,
|
||||
):
|
||||
calendars = await services.calendar.calendars_for_container(
|
||||
session, container_type, container_id,
|
||||
)
|
||||
return {"calendars": calendars, "post_slug": post_slug}
|
||||
|
||||
|
||||
# -- container_card: confirmed entries for post listings ----------------------
|
||||
|
||||
async def _card_entries_batch(session, post_ids):
|
||||
return await services.calendar.confirmed_entries_for_posts(session, post_ids)
|
||||
|
||||
|
||||
# -- account pages: tickets & bookings ---------------------------------------
|
||||
|
||||
async def _tickets_context(session, *, user_id, **kw):
|
||||
tickets = await services.calendar.user_tickets(session, user_id=user_id)
|
||||
return {"tickets": tickets}
|
||||
|
||||
|
||||
async def _bookings_context(session, *, user_id, **kw):
|
||||
bookings = await services.calendar.user_bookings(session, user_id=user_id)
|
||||
return {"bookings": bookings}
|
||||
|
||||
|
||||
# -- registration entry point ------------------------------------------------
|
||||
|
||||
def register_calendar_widgets() -> None:
|
||||
widgets.add_container_nav(NavWidget(
|
||||
domain="calendar",
|
||||
order=10,
|
||||
context_fn=_nav_entries_context,
|
||||
template="_widgets/container_nav/calendar_entries.html",
|
||||
))
|
||||
widgets.add_container_nav(NavWidget(
|
||||
domain="calendar_links",
|
||||
order=20,
|
||||
context_fn=_nav_calendars_context,
|
||||
template="_widgets/container_nav/calendar_links.html",
|
||||
))
|
||||
widgets.add_container_card(CardWidget(
|
||||
domain="calendar",
|
||||
order=10,
|
||||
batch_fn=_card_entries_batch,
|
||||
context_key="associated_entries",
|
||||
template="_widgets/container_card/calendar_entries.html",
|
||||
))
|
||||
widgets.add_account_page(AccountPageWidget(
|
||||
domain="calendar",
|
||||
slug="tickets",
|
||||
label="tickets",
|
||||
order=20,
|
||||
context_fn=_tickets_context,
|
||||
template="_types/auth/_tickets_panel.html",
|
||||
))
|
||||
widgets.add_account_page(AccountPageWidget(
|
||||
domain="calendar",
|
||||
slug="bookings",
|
||||
label="bookings",
|
||||
order=30,
|
||||
context_fn=_bookings_context,
|
||||
template="_types/auth/_bookings_panel.html",
|
||||
))
|
||||
15
services/widgets/cart_widgets.py
Normal file
15
services/widgets/cart_widgets.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Cart-domain widgets: orders link on account page."""
|
||||
from __future__ import annotations
|
||||
|
||||
from shared.contracts.widgets import AccountNavLink
|
||||
from shared.services.widget_registry import widgets
|
||||
from shared.infrastructure.urls import cart_url
|
||||
|
||||
|
||||
def register_cart_widgets() -> None:
|
||||
widgets.add_account_link(AccountNavLink(
|
||||
label="orders",
|
||||
order=100,
|
||||
href_fn=lambda: cart_url("/orders/"),
|
||||
external=True,
|
||||
))
|
||||
24
services/widgets/market_widgets.py
Normal file
24
services/widgets/market_widgets.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Market-domain widgets: marketplace links on container pages."""
|
||||
from __future__ import annotations
|
||||
|
||||
from shared.contracts.widgets import NavWidget
|
||||
from shared.services.widget_registry import widgets
|
||||
from shared.services.registry import services
|
||||
|
||||
|
||||
async def _nav_markets_context(
|
||||
session, *, container_type, container_id, post_slug, **kw,
|
||||
):
|
||||
markets = await services.market.marketplaces_for_container(
|
||||
session, container_type, container_id,
|
||||
)
|
||||
return {"markets": markets, "post_slug": post_slug}
|
||||
|
||||
|
||||
def register_market_widgets() -> None:
|
||||
widgets.add_container_nav(NavWidget(
|
||||
domain="market",
|
||||
order=30,
|
||||
context_fn=_nav_markets_context,
|
||||
template="_widgets/container_nav/market_links.html",
|
||||
))
|
||||
Reference in New Issue
Block a user