Refactor draft messaging to unified enum-based protocol
- Replaced scattered message strings with `DraftMessage` `StrEnum` and numeric `DraftPhase` `IntEnum` for clear, centralized definitions. - Added Python→JS constants sync via `scripts/generate_js_constants.py` to ensure backend/frontend parity. - Refactored WebSocket consumers to use `broadcast.*` and `direct.message` handlers with `_dispatch_broadcast` for consistent event delivery. - Enhanced `DraftStateManager` to store `draft_index` and explicitly manage `connected_participants`. - Added colored logging config in settings for improved debugging. - Frontend: split UI into `ParticipantList` and `DraftMoviePool`, extracted message handlers (`handleDraftStatusMessages`, `handleUserIdentifyMessages`), and updated components to use new message/phase enums.
This commit is contained in:
103
scripts/generate_js_constants.py
Normal file
103
scripts/generate_js_constants.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python
|
||||
import importlib
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
from enum import Enum, IntEnum, StrEnum
|
||||
|
||||
# Adjust these for your project
|
||||
PY_MODULE = "draft.constants" # where your enums live
|
||||
OUTPUT_PATH = "frontend/src/apps/draft/constants.js"
|
||||
|
||||
# Optionally allow running from any cwd
|
||||
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(PROJECT_ROOT, ".."))
|
||||
if PROJECT_ROOT not in sys.path:
|
||||
sys.path.insert(0, PROJECT_ROOT)
|
||||
|
||||
|
||||
def js_quote(s: str) -> str:
|
||||
return s.replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
|
||||
def titleize(name: str) -> str:
|
||||
# e.g., "DETERMINE_ORDER" -> "Determine Order"
|
||||
return name.replace("_", " ").title()
|
||||
|
||||
|
||||
def emit_header():
|
||||
return "// AUTO-GENERATED. Do not edit by hand.\n" \
|
||||
"// Run: python scripts/generate_js_constants.py\n\n"
|
||||
|
||||
|
||||
def emit_str_enum(name: str, enum_cls) -> str:
|
||||
"""
|
||||
Emit a JS object for StrEnum:
|
||||
export const DraftMessage = { KEY: "value", ... };
|
||||
"""
|
||||
lines = [f"export const {name} = {{"] # ESM export
|
||||
for member in enum_cls:
|
||||
lines.append(f' {member.name}: "{js_quote(member.value)}",')
|
||||
lines.append("};\n")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def emit_int_enum(name: str, enum_cls) -> str:
|
||||
"""
|
||||
Emit a JS object + labels + ordered list for IntEnum:
|
||||
export const DraftPhase = { KEY: number, ... };
|
||||
export const DraftPhaseLabel = { [number]: "Pretty", ... };
|
||||
export const DraftPhasesOrdered = [numbers...];
|
||||
"""
|
||||
lines = [f"export const {name} = {{"] # ESM export
|
||||
items = list(enum_cls)
|
||||
# object map
|
||||
for member in items:
|
||||
lines.append(f" {member.name}: {int(member.value)},")
|
||||
lines.append("};\n")
|
||||
|
||||
# label map (use .pretty_name if you added it; else derive from name or __str__)
|
||||
lines.append(f"export const {name}Label = {{")
|
||||
for member in items:
|
||||
if hasattr(member, "pretty_name"):
|
||||
label = getattr(member, "pretty_name")
|
||||
else:
|
||||
# fall back: __str__ if you overload it, else Title Case of name
|
||||
label = str(member)
|
||||
if label == f"{enum_cls.__name__}.{member.name}":
|
||||
label = titleize(member.name)
|
||||
lines.append(f' [{name}.{member.name}]: "{js_quote(label)}",')
|
||||
lines.append("};\n")
|
||||
|
||||
# ordered list
|
||||
ordered = sorted(items, key=lambda m: int(m.value))
|
||||
ordered_vals = ", ".join(f"{name}.{m.name}" for m in ordered)
|
||||
lines.append(f"export const {name}sOrdered = [{ordered_vals}];\n")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
mod = importlib.import_module(PY_MODULE)
|
||||
out = [emit_header()]
|
||||
|
||||
# Pick which enums to export. You can filter here if you don’t want all.
|
||||
for name, obj in inspect.getmembers(mod):
|
||||
ignore_classes = [Enum, IntEnum, StrEnum]
|
||||
if inspect.isclass(obj) and issubclass(obj, Enum) and not obj in ignore_classes:
|
||||
# Skip helper classes that aren’t actual Enums
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
if issubclass(obj, IntEnum):
|
||||
out.append(emit_int_enum(name, obj))
|
||||
else:
|
||||
out.append(emit_str_enum(name, obj))
|
||||
|
||||
os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True)
|
||||
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(out))
|
||||
|
||||
print(f"✅ Wrote {OUTPUT_PATH}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user