Send world/item UI metadata in welcome and consume on client
This commit is contained in:
@@ -6,6 +6,21 @@ from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
ItemType = Literal["radio_station", "dice", "wheel", "clock"]
|
||||
ITEM_TYPE_SEQUENCE: tuple[ItemType, ...] = ("clock", "dice", "radio_station", "wheel")
|
||||
ITEM_TYPE_LABELS: dict[ItemType, str] = {
|
||||
"radio_station": "radio",
|
||||
"dice": "dice",
|
||||
"wheel": "wheel",
|
||||
"clock": "clock",
|
||||
}
|
||||
RADIO_EFFECT_OPTIONS: tuple[str, ...] = ("reverb", "echo", "flanger", "high_pass", "low_pass", "off")
|
||||
RADIO_CHANNEL_OPTIONS: tuple[str, ...] = ("stereo", "mono", "left", "right")
|
||||
ITEM_TYPE_EDITABLE_PROPERTIES: dict[ItemType, tuple[str, ...]] = {
|
||||
"radio_station": ("title", "streamUrl", "enabled", "channel", "volume", "effect", "effectValue"),
|
||||
"dice": ("title", "sides", "number"),
|
||||
"wheel": ("title", "spaces"),
|
||||
"clock": ("title", "timeZone", "use24Hour"),
|
||||
}
|
||||
CLOCK_DEFAULT_TIME_ZONE = "America/Detroit"
|
||||
CLOCK_TIME_ZONE_OPTIONS: tuple[str, ...] = (
|
||||
"America/Anchorage",
|
||||
@@ -95,6 +110,12 @@ ITEM_DEFINITIONS: dict[ItemType, ItemDefinition] = {
|
||||
),
|
||||
}
|
||||
|
||||
ITEM_PROPERTY_OPTIONS: dict[str, tuple[str, ...]] = {
|
||||
"effect": RADIO_EFFECT_OPTIONS,
|
||||
"channel": RADIO_CHANNEL_OPTIONS,
|
||||
"timeZone": CLOCK_TIME_ZONE_OPTIONS,
|
||||
}
|
||||
|
||||
|
||||
def get_item_definition(item_type: ItemType) -> ItemDefinition:
|
||||
"""Return catalog definition for a known item type."""
|
||||
@@ -110,3 +131,14 @@ def get_item_use_cooldown_ms(item_type: ItemType) -> int:
|
||||
if isinstance(cooldown_ms, int) and cooldown_ms > 0:
|
||||
return cooldown_ms
|
||||
return 1000
|
||||
|
||||
|
||||
def get_item_global_properties(item_type: ItemType) -> dict[str, str | int]:
|
||||
"""Return non-editable global properties exposed in UI metadata."""
|
||||
|
||||
definition = get_item_definition(item_type)
|
||||
return {
|
||||
"useSound": definition.use_sound or "none",
|
||||
"emitSound": definition.emit_sound or "none",
|
||||
"useCooldownMs": get_item_use_cooldown_ms(item_type),
|
||||
}
|
||||
|
||||
@@ -101,6 +101,8 @@ class WelcomePacket(BasePacket):
|
||||
id: str
|
||||
users: list[RemoteUser]
|
||||
items: list[dict] | None = None
|
||||
worldConfig: dict | None = None
|
||||
uiDefinitions: dict | None = None
|
||||
|
||||
|
||||
class UserLeftPacket(BasePacket):
|
||||
|
||||
@@ -18,7 +18,16 @@ from websockets.asyncio.server import ServerConnection, serve
|
||||
|
||||
from .client import ClientConnection
|
||||
from .config import load_config
|
||||
from .item_catalog import CLOCK_DEFAULT_TIME_ZONE, CLOCK_TIME_ZONE_OPTIONS, get_item_use_cooldown_ms
|
||||
from .item_catalog import (
|
||||
CLOCK_DEFAULT_TIME_ZONE,
|
||||
CLOCK_TIME_ZONE_OPTIONS,
|
||||
ITEM_PROPERTY_OPTIONS,
|
||||
ITEM_TYPE_EDITABLE_PROPERTIES,
|
||||
ITEM_TYPE_LABELS,
|
||||
ITEM_TYPE_SEQUENCE,
|
||||
get_item_global_properties,
|
||||
get_item_use_cooldown_ms,
|
||||
)
|
||||
from .item_type_handlers import get_item_type_handler
|
||||
from .item_service import ItemService
|
||||
from .models import (
|
||||
@@ -236,9 +245,36 @@ class SignalingServer:
|
||||
id=client.id,
|
||||
users=users,
|
||||
items=[item.model_dump(exclude_none=True) for item in self.items.values()],
|
||||
worldConfig={"gridSize": self.grid_size},
|
||||
uiDefinitions=self._build_ui_definitions(),
|
||||
)
|
||||
await self._send(client.websocket, packet)
|
||||
|
||||
def _build_ui_definitions(self) -> dict:
|
||||
"""Build server-owned UI definitions for item/menu rendering."""
|
||||
|
||||
item_types: list[dict] = []
|
||||
for item_type in ITEM_TYPE_SEQUENCE:
|
||||
editable = list(ITEM_TYPE_EDITABLE_PROPERTIES.get(item_type, ("title",)))
|
||||
property_options: dict[str, list[str]] = {}
|
||||
for key in editable:
|
||||
options = ITEM_PROPERTY_OPTIONS.get(key)
|
||||
if options:
|
||||
property_options[key] = list(options)
|
||||
item_types.append(
|
||||
{
|
||||
"type": item_type,
|
||||
"label": ITEM_TYPE_LABELS.get(item_type, item_type),
|
||||
"editableProperties": editable,
|
||||
"propertyOptions": property_options,
|
||||
"globalProperties": get_item_global_properties(item_type),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"itemTypeOrder": list(ITEM_TYPE_SEQUENCE),
|
||||
"itemTypes": item_types,
|
||||
}
|
||||
|
||||
async def _broadcast_wheel_result_after_delay(
|
||||
self,
|
||||
client: ClientConnection,
|
||||
|
||||
Reference in New Issue
Block a user