Improve draft UI state handling, layout, and order logic

- Added current/next pick info, updated server draft logic for order/snake
- Refactored WebSocketContext, removed dead code, improved CSS/layout
- Cleaned up template blocks, admin, and participant panel structure
This commit is contained in:
2025-08-12 21:34:02 -05:00
parent cd4d974fce
commit 71f0f01abc
13 changed files with 246 additions and 322 deletions

View File

@@ -40,7 +40,7 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
self.group_names = DraftGroupChannelNames(draft_hashid)
self.cache_keys = DraftCacheKeys(draft_hashid)
self.draft_state = DraftStateManager(draft_hashid, self.draft_session.settings)
self.draft_state = DraftStateManager(self.draft_session)
self.user = self.scope["user"]
if not self.should_accept_user():
@@ -172,7 +172,7 @@ class DraftAdminConsumer(DraftConsumerBase):
await self.start_nominate()
if event_type == DraftMessage.DRAFT_INDEX_ADVANCE_REQUEST:
self.draft_state.draft_index += 1
self.draft_state.draft_index_advance()
await self.channel_layer.group_send(
self.group_names.session,
{
@@ -229,18 +229,22 @@ class DraftAdminConsumer(DraftConsumerBase):
)
async def determine_draft_order(self):
draft_order = random.sample(
self.draft_participants, len(self.draft_participants)
)
self.draft_state.draft_order = [p.username for p in draft_order]
draft_order = self.draft_state.determine_draft_order(self.draft_participants)
self.draft_state.draft_index = 0
await self.set_draft_phase(DraftPhase.DETERMINE_ORDER)
next_picks = self.draft_state.next_picks(include_current=True)
await self.channel_layer.group_send(
self.group_names.session,
{
"type": "broadcast.session",
"subtype": DraftMessage.ORDER_DETERMINE_CONFIRM,
"payload": {"draft_order": self.draft_state.draft_order},
"payload": {
"draft_order": draft_order,
"draft_index": self.draft_state.draft_index,
"current_pick": next_picks[0],
"next_picks": next_picks[1:]
},
},
)

View File

@@ -4,8 +4,11 @@ from datetime import datetime, timedelta
from boxofficefantasy.models import Movie
from django.contrib.auth.models import User
from draft.constants import DraftPhase
from draft.models import DraftSessionSettings
from draft.models import DraftSession
import time
from dataclasses import dataclass
from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple
import random
class DraftCacheKeys:
def __init__(self, id):
@@ -73,12 +76,12 @@ class DraftCacheKeys:
# return f"{self.prefix}:user:{user_id}:channel"
class DraftStateManager:
def __init__(self, session_id: int, settings: DraftSessionSettings):
self.session_id = session_id
def __init__(self, session: DraftSession):
self.session_id = session.hashid
self.cache = cache
self.keys = DraftCacheKeys(session_id)
self.keys = DraftCacheKeys(self.session_id)
self._initial_phase = self.cache.get(self.keys.phase, DraftPhase.WAITING.value)
self.settings = settings
self.settings = session.settings
# === Phase Management ===
@property
@@ -114,6 +117,13 @@ class DraftStateManager:
if not isinstance(draft_order, list):
return
self.cache.set(self.keys.draft_order,json.dumps(draft_order))
def determine_draft_order(self, users: list[User]):
draft_order = random.sample(
users, len(users)
)
self.draft_order = [user.username for user in draft_order]
return self.draft_order
@property
def draft_index(self):
@@ -122,6 +132,42 @@ class DraftStateManager:
@draft_index.setter
def draft_index(self, draft_index: int):
self.cache.set(self.keys.draft_index, int(draft_index))
def draft_index_advance(self, n: int = 1):
self.draft_index += n
return self.draft_index
def next_picks(
self,
*,
from_overall: int | None = None,
count: int | None = None,
include_current: bool = False,
) -> List[dict]:
"""
Convenience: return the next `count` picks starting after `from_overall`
(or after current draft_index if omitted). Each item:
{overall, round, pick_in_round, participant}
"""
if not self.draft_order:
return []
n = len(self.draft_order)
count = count if count else len(self.draft_order)
start = self.draft_index if from_overall is None else int(from_overall)
start = start if include_current else start + 1
out: List[dict] = []
for overall in range(start, start + count):
r, p = _round_and_pick(overall, n)
order_type = "snake"
order = _round_order(r, order_type, self.draft_order)
out.append({
"overall": overall,
"round": r,
"pick_in_round": p,
"participant": order[p - 1],
})
return out
# === Current Nomination / Bid ===
def start_nomination(self, movie_id: int):
@@ -155,6 +201,7 @@ class DraftStateManager:
# === Sync Snapshot ===
def get_summary(self) -> dict:
picks = self.next_picks(include_current=True)
return {
"phase": self.phase,
"draft_order": self.draft_order,
@@ -164,4 +211,18 @@ class DraftStateManager:
# "bids": self.get_bids(),
"bidding_timer_end": self.get_timer_end(),
"bidding_timer_start": self.get_timer_start(),
}
"current_pick": picks[0] if picks else None,
"next_picks": picks[1:] if picks else []
}
OrderType = Literal["snake", "linear"]
def _round_and_pick(overall: int, n: int) -> Tuple[int, int]:
"""overall -> (round_1_based, pick_in_round_1_based)"""
r = overall // n + 1
p = overall % n + 1
return r, p
def _round_order(round_num: int, order_type: OrderType, r1: Sequence[Any]) -> Sequence[Any]:
if order_type == "linear" or (round_num % 2 == 1):
return r1
return list(reversed(r1)) # even rounds in snake

View File

@@ -1,14 +1,8 @@
{% extends "base.dj.html" %}
{% block content %}
<h1>Draft Room: {{ league.name }} {{ season.label }} {{ season.year }}</h1>
{% block body %}
{% load static %}
<script>
window.draftSessionId = "{{ draft_id_hashed }}"
</script>
<div id="draft-participant-root" data-draft-id="{{ draft_id_hashed }}"></div>
{% if DEBUG %}
<script src="http://localhost:3000/dist/bundle.js"></script>
{% else %}
<script src="{% static 'bundle.js' %}"></script>
{% endif %}
{% endblock content %}
{% endblock body %}