diff --git a/data/cache_concept.py b/data/cache_concept.py
new file mode 100644
index 0000000..b85584d
--- /dev/null
+++ b/data/cache_concept.py
@@ -0,0 +1,75 @@
+import pickle
+import os
+from typing import Any
+from pathlib import Path
+import json
+
+DEFAULT_PATH = Path("/Users/asc/Developer/boxofficefantasy/main/data/draft_cache.json")
+
+class CachedDraftState:
+ participants: list
+ phase: str # Replace with Enum if needed
+ draft_order: list = []
+ draft_index: int
+ current_movie: str
+ bids: list
+
+ def __init__(self, cache_file: str = "draft_cache.json"):
+ super().__setattr__("_cache_file", cache_file)
+ super().__setattr__("_cache", self._load_cache())
+
+ def _load_cache(self) -> dict:
+ if os.path.exists(self._cache_file):
+ try:
+ with open(self._cache_file, "r", encoding="utf-8") as f:
+ return json.load(f)
+ except Exception as e:
+ print(f"Failed to load cache: {e}")
+ return {}
+ return {}
+
+ def _save_cache(self):
+ try:
+ with open(self._cache_file, "w", encoding="utf-8") as f:
+ json.dump(self._cache, f, indent=2)
+ except Exception as e:
+ print(f"Failed to save cache: {e}")
+
+ def __getattr__(self, name: str) -> Any:
+ if name in self.__class__.__annotations__:
+ print(f"[GET] {name} -> {self._cache.get(name)}")
+ return self._cache.get(name, None)
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
+
+ def __setattr__(self, name: str, value: Any):
+ if name in self.__class__.__annotations__:
+ print(f"[SET] {name} = {value}")
+ self._cache[name] = value
+ self._save_cache()
+ else:
+ super().__setattr__(name, value)
+
+if __name__ == "__main__":
+ # Clean start for testing
+ if os.path.exists("draft_cache.pkl"):
+ os.remove("draft_cache.pkl")
+
+ print("\n--- First Run: Setting Attributes ---")
+ state = CachedDraftState()
+ state.participants = ["Alice", "Bob"]
+ state.phase = "nominating"
+ # state.draft_order = ["Bob", "Alice"]
+ state.draft_index = 0
+ state.current_movie = "The Matrix"
+ state.bids = [{"Alice": 10}, {"Bob": 12}]
+
+ print("\n--- Second Run: Reading from Cache ---")
+ state2 = CachedDraftState()
+ print("participants:", state2.participants)
+ print("phase:", state2.phase)
+ print("draft_order:", state2.draft_order)
+ print("draft_index:", state2.draft_index)
+ print("current_movie:", state2.current_movie)
+ print("bids:", state2.bids)
+
+ pass
\ No newline at end of file
diff --git a/data/draft_cache.json b/data/draft_cache.json
new file mode 100644
index 0000000..23eef64
Binary files /dev/null and b/data/draft_cache.json differ
diff --git a/draft/constants.py b/draft/constants.py
index a308a31..db339bc 100644
--- a/draft/constants.py
+++ b/draft/constants.py
@@ -18,8 +18,8 @@ class DraftMessage(StrEnum):
PHASE_CHANGE_CONFIRM = "phase.change.confirm" # server -> client (target phase payload)
# Status / sync
- STATUS_SYNC_REQUEST = "status.sync.request" # client -> server
- STATUS_SYNC_INFORM = "status.sync.inform" # server -> client (full/partial state)
+ DRAFT_STATUS_REQUEST = "draft.status.request" # client -> server
+ DRAFT_STATUS_INFORM = "draft.status.sync.inform" # server -> client (full/partial state)
DRAFT_INDEX_ADVANCE_REQUEST = "draft.index.advance.request"
DRAFT_INDEX_ADVANCE_CONFIRM = "draft.index.advance.confirm"
diff --git a/draft/consumers.py b/draft/consumers.py
index aa626bf..f1a1875 100644
--- a/draft/consumers.py
+++ b/draft/consumers.py
@@ -72,18 +72,12 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
"payload": {"user": self.user.username},
},
)
- await self.channel_layer.send(
- self.channel_name,
+ await self.channel_layer.group_send(
+ self.group_names.session,
{
"type": "direct.message",
- "subtype": DraftMessage.STATUS_SYNC_INFORM,
- "payload": {
- **self.draft_state,
- "user": self.user.username,
- "participants": [
- user.username for user in self.draft_participants
- ],
- },
+ "subtype": DraftMessage.DRAFT_STATUS_INFORM,
+ "payload": self.draft_state.to_dict(),
},
)
await self.channel_layer.send(
@@ -101,14 +95,37 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
async def receive_json(self, content):
logger.info(f"receiving message {content}")
event_type = content.get("type")
- if event_type == DraftMessage.STATUS_SYNC_REQUEST:
+ if event_type == DraftMessage.DRAFT_STATUS_REQUEST:
await self.send_json(
{
- "type": DraftMessage.STATUS_SYNC_INFORM,
+ "type": DraftMessage.DRAFT_STATUS_INFORM,
"payload": self.get_draft_status(),
}
)
+ # --- Convenience helpers ---
+ async def send_draft_state(self):
+ """Send the current draft state only to this client."""
+ await self.channel_layer.send(
+ self.channel_name,
+ {
+ "type": "direct.message",
+ "subtype": DraftMessage.DRAFT_STATUS_INFORM,
+ "payload": self.draft_state.to_dict(),
+ },
+ )
+
+ async def broadcast_state(self):
+ """Broadcast current draft state to all in session group."""
+ await self.channel_layer.group_send(
+ self.group_names.session,
+ {
+ "type": "broadcast.session",
+ "subtype": DraftMessage.DRAFT_STATUS_INFORM,
+ "payload": self.draft_state.to_dict(),
+ },
+ )
+
# Broadcast Handlers
async def direct_message(self, event):
await self._dispatch_broadcast(event)
@@ -132,9 +149,15 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
def get_draft_session(self, draft_session_id_hashed) -> DraftSession:
draft_session_id = DraftSession.decode_id(draft_session_id_hashed)
if draft_session_id:
- draft_session = DraftSession.objects.select_related(
- "season", "season__league", "settings",
- ).prefetch_related("participants").get(pk=draft_session_id)
+ draft_session = (
+ DraftSession.objects.select_related(
+ "season",
+ "season__league",
+ "settings",
+ )
+ .prefetch_related("participants")
+ .get(pk=draft_session_id)
+ )
else:
raise Exception()
@@ -155,89 +178,85 @@ class DraftAdminConsumer(DraftConsumerBase):
await self.channel_layer.group_add(self.group_names.admin, self.channel_name)
+ def should_accept_user(self):
+ return super().should_accept_user() and self.user.is_staff
+
async def receive_json(self, content):
await super().receive_json(content)
logger.info(f"Receive message {content}")
event_type = content.get("type")
- if (
- event_type == DraftMessage.PHASE_CHANGE_REQUEST
- and content.get("destination") == DraftPhase.DETERMINE_ORDER
- ):
- await self.determine_draft_order()
- if (
- event_type == DraftMessage.PHASE_CHANGE_REQUEST
- and content.get("destination") == DraftPhase.NOMINATING
- ):
- await self.start_nominate()
+ match event_type:
+ case DraftMessage.PHASE_CHANGE_REQUEST:
+ destination = content.get('destination')
+ match destination:
+ case DraftPhase.DETERMINE_ORDER:
+ await self.set_draft_phase(DraftPhase.DETERMINE_ORDER)
+ self.draft_state.determine_draft_order()
+ 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},
+ },
+ )
+ await self.broadcast_state()
- if event_type == DraftMessage.DRAFT_INDEX_ADVANCE_REQUEST:
- self.draft_state.draft_index_advance()
- await self.channel_layer.group_send(
- self.group_names.session,
- {
- "type": "broadcast.session",
- "subtype": DraftMessage.DRAFT_INDEX_ADVANCE_CONFIRM,
- "payload": {**self.draft_state},
- },
- )
+ case DraftPhase.NOMINATING:
+ await self.set_draft_phase(DraftPhase.NOMINATING)
+ await self.channel_layer.group_send(
+ self.group_names.session,
+ {
+ "type": "broadcast.session",
+ "subtype": DraftMessage.PHASE_CHANGE_CONFIRM,
+ "payload": {"phase": self.draft_state.phase},
+ },
+ )
+ await self.broadcast_state()
- if event_type == DraftMessage.NOMINATION_SUBMIT_REQUEST:
- movie_id = content.get("payload", {}).get("movie_id")
- user = content.get("payload", {}).get("user")
- self.draft_state.start_nomination(movie_id)
- await self.channel_layer.group_send(
- self.group_names.session,
- {
- "type": "broadcast.session",
- "subtype": DraftMessage.NOMINATION_CONFIRM,
- "payload": {
- "current_movie": self.draft_state[
- "current_movie"
- ],
- "nominating_participant": user,
+ case DraftMessage.DRAFT_INDEX_ADVANCE_REQUEST:
+ self.draft_state.draft_index_advance()
+ await self.channel_layer.group_send(
+ self.group_names.session,
+ {
+ "type": "broadcast.session",
+ "subtype": DraftMessage.DRAFT_INDEX_ADVANCE_CONFIRM,
+ "payload": {"draft_index": self.draft_state.draft_index},
},
- },
- )
- if event_type == DraftMessage.BID_START_REQUEST:
+ )
+ await self.broadcast_state()
- self.draft_state.start_bidding()
- await self.channel_layer.group_send(
- self.group_names.session,
- {
- "type": "broadcast.session",
- "subtype": DraftMessage.BID_START_INFORM,
- "payload": {**self.draft_state},
- },
- )
+ case DraftMessage.NOMINATION_SUBMIT_REQUEST:
+ movie_id = content.get("payload", {}).get("movie_id")
+ user = content.get("payload", {}).get("user")
+ self.draft_state.start_nomination(movie_id)
+ await self.channel_layer.group_send(
+ self.group_names.session,
+ {
+ "type": "broadcast.session",
+ "subtype": DraftMessage.NOMINATION_CONFIRM,
+ "payload": {
+ "current_movie": self.draft_state["current_movie"],
+ "nominating_participant": user,
+ },
+ },
+ )
+ await self.broadcast_state()
- def should_accept_user(self):
- return super().should_accept_user() and self.user.is_staff
+ case DraftMessage.BID_START_REQUEST:
+ self.draft_state.start_bidding()
+ await self.channel_layer.group_send(
+ self.group_names.session,
+ {
+ "type": "broadcast.session",
+ "subtype": DraftMessage.BID_START_INFORM,
+ "payload": {**self.draft_state},
+ },
+ )
+ await self.broadcast_state()
- # === Draft logic ===
- async def start_nominate(self):
- await self.set_draft_phase(DraftPhase.NOMINATING)
- await self.channel_layer.group_send(
- self.group_names.session,
- {
- "type": "broadcast.session",
- "subtype": DraftMessage.PHASE_CHANGE_CONFIRM,
- "payload": {"phase": self.draft_state.phase},
- },
- )
-
- async def determine_draft_order(self):
- self.draft_state.determine_draft_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": {**self.draft_state},
- },
- )
+ # === Draft logic ===
async def set_draft_phase(self, destination: DraftPhase):
self.draft_state.phase = destination
@@ -269,7 +288,9 @@ class DraftParticipantConsumer(DraftConsumerBase):
"subtype": DraftMessage.PARTICIPANT_JOIN_CONFIRM,
"payload": {
"user": self.user.username,
- "connected_participants": list(self.draft_state.connected_participants),
+ "connected_participants": list(
+ self.draft_state.connected_participants
+ ),
},
},
)
@@ -287,7 +308,9 @@ class DraftParticipantConsumer(DraftConsumerBase):
"subtype": DraftMessage.PARTICIPANT_LEAVE_INFORM,
"payload": {
"user": self.user.username,
- "connected_participants": list(self.draft_state.connected_participants),
+ "connected_participants": list(
+ self.draft_state.connected_participants
+ ),
},
},
)
@@ -315,9 +338,9 @@ class DraftParticipantConsumer(DraftConsumerBase):
},
},
)
-
+
if event_type == DraftMessage.BID_PLACE_REQUEST:
- bid_amount = content.get('payload',{}).get('bid_amount')
+ bid_amount = content.get("payload", {}).get("bid_amount")
self.draft_state.place_bid(self.user, bid_amount)
await self.channel_layer.group_send(
self.group_names.session,
diff --git a/draft/state.py b/draft/state.py
index 7011e27..86df768 100644
--- a/draft/state.py
+++ b/draft/state.py
@@ -65,10 +65,11 @@ class DraftCache:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
class DraftStateManager:
+ _initial_phase: DraftPhase = DraftPhase.WAITING.value
+
def __init__(self, session: DraftSession):
self.session_id: str = session.hashid
self.cache: DraftCache = DraftCache(self.session_id, cache)
- self._initial_phase: DraftPhase = self.cache.phase or DraftPhase.WAITING.value
self.settings: DraftSessionSettings = session.settings
self.participants: set[User] = set(session.participants.all())
self.connected_participants: set[User] = set()
@@ -76,7 +77,7 @@ class DraftStateManager:
# === Phase Management ===
@property
def phase(self) -> str:
- return self.cache.phase
+ return self.cache.phase or self._initial_phase
@phase.setter
def phase(self, new_phase: DraftPhase) -> None:
@@ -106,7 +107,7 @@ class DraftStateManager:
self.phase = DraftPhase.DETERMINE_ORDER
self.draft_index = 0
draft_order = random.sample(
- self.participants, len(self.participants)
+ list(self.participants), len(self.participants)
)
self.draft_order = [user.username for user in draft_order]
return self.draft_order
diff --git a/draft/templates/draft/room.dj.html b/draft/templates/draft/room.dj.html
index 7dbb4c0..9aa5f24 100644
--- a/draft/templates/draft/room.dj.html
+++ b/draft/templates/draft/room.dj.html
@@ -3,6 +3,10 @@
{% load static %}
+{% if user.is_staff %}
+You are admin!
+{% endif %}
{% endblock body %}
\ No newline at end of file
diff --git a/draft/urls.py b/draft/urls.py
index 71c420f..aaa63ee 100644
--- a/draft/urls.py
+++ b/draft/urls.py
@@ -6,6 +6,7 @@ app_name = "draft"
urlpatterns = [
# path("", views.draft_room, name="room"),
path("session//", views.draft_room, name="session"),
- path("session//", views.draft_room, name="admin_session"),
+ path("session//debug", views.draft_room_debug, name="session"),
+ # path("session//", views.draft_room, name="admin_session"),
# path("//", views.draft_room_list, name="room"),
]
\ No newline at end of file
diff --git a/draft/views.py b/draft/views.py
index 6e77179..fe62e26 100644
--- a/draft/views.py
+++ b/draft/views.py
@@ -6,28 +6,22 @@ from django.contrib.auth.decorators import login_required
from boxofficefantasy_project.utils import decode_id
@login_required(login_url='/login/')
-def draft_room(request, league_slug=None, season_slug=None, draft_session_id_hashed=None, subpage=""):
+def draft_room(request, draft_session_id_hashed=None):
if draft_session_id_hashed:
draft_session_id = decode_id(draft_session_id_hashed)
draft_session = get_object_or_404(DraftSession, id=draft_session_id)
league = draft_session.season.league
season = draft_session.season
- elif league_slug and season_slug:
- raise NotImplementedError
- league = get_object_or_404(League, slug=league_slug)
- label, year = parse_season_slug(season_slug)
- season = get_object_or_404(Season, league=league, label__iexact=label, year=year)
- draft_session = get_object_or_404(DraftSession, season=season)
context = {
"draft_id_hashed": draft_session.hashid,
"league": league,
"season": season,
}
+ return render(request, "draft/room.dj.html", context)
- if subpage == "admin":
- return render(request, "draft/room_admin.dj.html", context)
- elif subpage == "debug":
- return render(request, "draft/room_debug.dj.html", context)
- else:
- return render(request, "draft/room.dj.html", context)
+def draft_room_debug(request, draft_session_id_hashed=None):
+ if draft_session_id_hashed:
+ draft_session_id = decode_id(draft_session_id_hashed)
+ draft_session = get_object_or_404(DraftSession, id=draft_session_id)
+ return render(request, "draft/room_debug.dj.html", {"draft_id_hashed": draft_session.hashid,})
\ No newline at end of file
diff --git a/draft_cache.json b/draft_cache.json
new file mode 100644
index 0000000..b70d1cf
--- /dev/null
+++ b/draft_cache.json
@@ -0,0 +1,21 @@
+{
+ "participants": [
+ "Alice",
+ "Bob"
+ ],
+ "phase": "nominating",
+ "draft_order": [
+ "Bob",
+ "Alice"
+ ],
+ "draft_index": 0,
+ "current_movie": "The Matrix",
+ "bids": [
+ {
+ "Alice": 10
+ },
+ {
+ "Bob": 12
+ }
+ ]
+}
\ No newline at end of file
diff --git a/frontend/src/apps/draft/admin/DraftAdmin.jsx b/frontend/src/apps/draft/DraftAdminBar.jsx
similarity index 84%
rename from frontend/src/apps/draft/admin/DraftAdmin.jsx
rename to frontend/src/apps/draft/DraftAdminBar.jsx
index af4e283..ef708e8 100644
--- a/frontend/src/apps/draft/admin/DraftAdmin.jsx
+++ b/frontend/src/apps/draft/DraftAdminBar.jsx
@@ -1,13 +1,10 @@
import React, { useEffect, useState } from "react";
-import { useWebSocket } from "../common/WebSocketContext.jsx";
-import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
-import { ParticipantList } from "../common/ParticipantList.jsx";
-import { DraftMessage, DraftPhase, DraftPhaseLabel, DraftPhasesOrdered } from '../constants.js';
-import { fetchDraftDetails, isEmptyObject, handleDraftStatusMessages, handleUserIdentifyMessages } from "../common/utils.js"
-import { DraftMoviePool } from "../common/DraftMoviePool.jsx"
-import { DraftCountdownClock } from "../common/DraftCountdownClock.jsx"
-import { DraftParticipant } from "../participant/DraftParticipant.jsx";
+import { useWebSocket } from "./components/WebSocketContext.jsx";
+
+import { DraftMessage, DraftPhase, DraftPhaseLabel, DraftPhasesOrdered } from './constants.js';
+import { fetchDraftDetails, isEmptyObject, handleDraftStatusMessages, handleUserIdentifyMessages } from "./utils.js"
+
import { jsxs } from "react/jsx-runtime";
@@ -102,7 +99,6 @@ export const DraftAdmin = ({ draftSessionId }) => {
else if (target == "previous" && originPhaseIndex > 0) {
destination = DraftPhasesOrdered[originPhaseIndex - 1]
}
- console.log(destination)
socket.send(
JSON.stringify(
{ type: DraftMessage.PHASE_CHANGE_REQUEST, origin, destination }
@@ -140,22 +136,15 @@ export const DraftAdmin = ({ draftSessionId }) => {
}
return (
-
-
-
-
+
+
-
-
-
+
+
{ handlePhaseChange('next') }} prevPhaseHandler={() => { handlePhaseChange('previous') }}>
diff --git a/frontend/src/apps/draft/participant/DraftParticipant.jsx b/frontend/src/apps/draft/DraftDashboard.jsx
similarity index 50%
rename from frontend/src/apps/draft/participant/DraftParticipant.jsx
rename to frontend/src/apps/draft/DraftDashboard.jsx
index 77d8ce4..4ebca86 100644
--- a/frontend/src/apps/draft/participant/DraftParticipant.jsx
+++ b/frontend/src/apps/draft/DraftDashboard.jsx
@@ -1,14 +1,14 @@
// DraftAdmin.jsx
import React, { useEffect, useState, useRef } from "react";
-import { useWebSocket } from "../common/WebSocketContext.jsx";
-import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
-import { DraftMessage, DraftPhaseLabel, DraftPhases } from '../constants.js';
-import { fetchDraftDetails, handleUserIdentifyMessages, isEmptyObject } from "../common/utils.js";
-import { DraftMoviePool } from "../common/DraftMoviePool.jsx";
-import { ParticipantList } from "../common/ParticipantList.jsx";
-import { DraftCountdownClock } from "../common/DraftCountdownClock.jsx"
-import { handleDraftStatusMessages } from '../common/utils.js'
+import { useWebSocket } from "./components/WebSocketContext.jsx";
+import { WebSocketStatus } from "./components/WebSocketStatus.jsx";
+import { DraftMessage, DraftPhaseLabel, DraftPhases } from './constants.js';
+import { fetchDraftDetails, handleUserIdentifyMessages, isEmptyObject } from "./utils.js";
+import { DraftMoviePool } from "./components/DraftMoviePool.jsx";
+import { ParticipantList } from "./components/ParticipantList.jsx";
+import { DraftCountdownClock } from "./components/DraftCountdownClock.jsx"
+import { handleDraftStatusMessages } from './utils.js'
// import { Collapse } from 'bootstrap/dist/js/bootstrap.bundle.min.js';
import { Collapse, ListGroup } from "react-bootstrap";
@@ -62,6 +62,7 @@ const NominateMenu = ({ socket, draftState, draftDetails, currentUser, }) => {
}
export const DraftParticipant = ({ draftSessionId }) => {
+
const socket = useWebSocket();
const [draftState, setDraftState] = useState({});
const [draftDetails, setDraftDetails] = useState({});
@@ -79,13 +80,6 @@ export const DraftParticipant = ({ draftSessionId }) => {
})
}, [draftSessionId])
- useEffect(() => {
- if (!socket) return;
- socket.onclose = (event) => {
- console.log('Websocket Closed')
- }
- }, [socket])
-
useEffect(() => {
if (!socket) return;
@@ -116,80 +110,88 @@ export const DraftParticipant = ({ draftSessionId }) => {
return (
-
-
-
-
-
-
- {console.log("draft_state", draftState)}
-
Round {draftState.current_pick?.round}
-
Pick {draftState.current_pick?.pick_in_round}
-
{draftState.current_pick?.overall + 1} Overall
+
+
-
-
-
- {draftState.bids?.length > 0 ? Math.max(draftState.bids?.map(i=>i.bid_amount)) : ""}
+
+
+
+
+
+
Round {draftState.current_pick?.round}
+
Pick {draftState.current_pick?.pick_in_round}
+
{draftState.current_pick?.overall + 1} Overall
-
- highest bid
+
+
+
+
Show Content
+
+
+
+
Bids
+
+
+ {draftState.bids?.reverse().map((b,idx) => (
+ -
+
+
{b.user}
+
{b.amount}
+
+
+ ))}
+
+
+
+
+
+
-
- {draftState.bids?.map((bid, idx) => (
- - {bid.user}: {bid.amount}
- ))}
-
+
-
-
-
-
-
+
- Draft Catalog
+ Draft Catalog
Current Nomination: {movies.find(i => draftState.current_movie == i.id)?.title}
-
+ {/*
*/}
@@ -199,7 +201,7 @@ export const DraftParticipant = ({ draftSessionId }) => {
@@ -212,21 +214,18 @@ export const DraftParticipant = ({ draftSessionId }) => {
-
- -
-
-
-
+ {draftState.participants?.map(p => (
+ -
+
{p}
+
+
+ ))}
diff --git a/frontend/src/apps/draft/DraftDebug.jsx b/frontend/src/apps/draft/DraftDebug.jsx
index 544c088..da0afcf 100644
--- a/frontend/src/apps/draft/DraftDebug.jsx
+++ b/frontend/src/apps/draft/DraftDebug.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";;
-import { useWebSocket } from "./common/WebSocketContext.jsx";
+import { useWebSocket } from "./components/WebSocketContext.jsx";
import { DraftMessage, DraftPhase, DraftPhaseLabel, DraftPhasesOrdered } from './constants.js';
-import { fetchDraftDetails, isEmptyObject, handleDraftStatusMessages, handleUserIdentifyMessages } from "./common/utils.js"
+import { fetchDraftDetails, isEmptyObject, handleDraftStatusMessages, handleUserIdentifyMessages } from "./utils.js"
export const DraftDebug = ({ draftSessionId }) => {
const [draftState, setDraftState] = useState({})
diff --git a/frontend/src/apps/draft/common/DraftCountdownClock.jsx b/frontend/src/apps/draft/components/DraftCountdownClock.jsx
similarity index 73%
rename from frontend/src/apps/draft/common/DraftCountdownClock.jsx
rename to frontend/src/apps/draft/components/DraftCountdownClock.jsx
index 2d25a8f..eeb4fed 100644
--- a/frontend/src/apps/draft/common/DraftCountdownClock.jsx
+++ b/frontend/src/apps/draft/components/DraftCountdownClock.jsx
@@ -1,10 +1,10 @@
import React, { useEffect, useState } from "react";
-export function DraftCountdownClock({ endTime, onFinish }) {
+export function DraftCountdownClock({ draftState }) {
// endTime is in seconds (Unix time)
-
+ const {bidding_timer_end, onFinish} = draftState
const getTimeLeft = (et) => Math.max(0, Math.floor(et - Date.now() / 1000));
- const [timeLeft, setTimeLeft] = useState(getTimeLeft(endTime));
+ const [timeLeft, setTimeLeft] = useState(getTimeLeft(bidding_timer_end));
useEffect(() => {
if (timeLeft <= 0) {
@@ -12,13 +12,13 @@ export function DraftCountdownClock({ endTime, onFinish }) {
return;
}
const timer = setInterval(() => {
- const t = getTimeLeft(endTime);
+ const t = getTimeLeft(bidding_timer_end);
setTimeLeft(t);
if (t <= 0 && onFinish) onFinish();
}, 100);
return () => clearInterval(timer);
// eslint-disable-next-line
- }, [endTime, onFinish, timeLeft]);
+ }, [bidding_timer_end, onFinish, timeLeft]);
const minutes = Math.floor(timeLeft / 60);
const secs = timeLeft % 60;
diff --git a/frontend/src/apps/draft/common/DraftMoviePool.jsx b/frontend/src/apps/draft/components/DraftMoviePool.jsx
similarity index 93%
rename from frontend/src/apps/draft/common/DraftMoviePool.jsx
rename to frontend/src/apps/draft/components/DraftMoviePool.jsx
index 00a445d..cc733d4 100644
--- a/frontend/src/apps/draft/common/DraftMoviePool.jsx
+++ b/frontend/src/apps/draft/components/DraftMoviePool.jsx
@@ -1,5 +1,5 @@
import React from "react";
-import { isEmptyObject } from "./utils";
+import { isEmptyObject } from "../utils";
export const DraftMoviePool = ({ isParticipant, draftDetails, draftState }) => {
if(isEmptyObject(draftDetails)) {return}
diff --git a/frontend/src/apps/draft/common/ParticipantList.jsx b/frontend/src/apps/draft/components/ParticipantList.jsx
similarity index 94%
rename from frontend/src/apps/draft/common/ParticipantList.jsx
rename to frontend/src/apps/draft/components/ParticipantList.jsx
index ee1363b..0bd97c4 100644
--- a/frontend/src/apps/draft/common/ParticipantList.jsx
+++ b/frontend/src/apps/draft/components/ParticipantList.jsx
@@ -1,5 +1,5 @@
import React from "react";
-import { fetchDraftDetails, isEmptyObject } from "../common/utils.js"
+import { fetchDraftDetails, isEmptyObject } from "../utils.js"
export const ParticipantList = ({ isAdmin, draftState, draftDetails, currentUser }) => {
if (isEmptyObject(draftState) || isEmptyObject(draftDetails)) { console.warn('empty draft state', draftState); return }
diff --git a/frontend/src/apps/draft/common/WebSocketContext.jsx b/frontend/src/apps/draft/components/WebSocketContext.jsx
similarity index 100%
rename from frontend/src/apps/draft/common/WebSocketContext.jsx
rename to frontend/src/apps/draft/components/WebSocketContext.jsx
diff --git a/frontend/src/apps/draft/common/WebSocketStatus.jsx b/frontend/src/apps/draft/components/WebSocketStatus.jsx
similarity index 100%
rename from frontend/src/apps/draft/common/WebSocketStatus.jsx
rename to frontend/src/apps/draft/components/WebSocketStatus.jsx
diff --git a/frontend/src/apps/draft/constants.js b/frontend/src/apps/draft/constants.js
index a7c8def..fddd87e 100644
--- a/frontend/src/apps/draft/constants.js
+++ b/frontend/src/apps/draft/constants.js
@@ -13,8 +13,8 @@ export const DraftMessage = {
PHASE_CHANGE_INFORM: "phase.change.inform",
PHASE_CHANGE_REQUEST: "phase.change.request",
PHASE_CHANGE_CONFIRM: "phase.change.confirm",
- STATUS_SYNC_REQUEST: "status.sync.request",
- STATUS_SYNC_INFORM: "status.sync.inform",
+ DRAFT_STATUS_REQUEST: "draft.status.request",
+ DRAFT_STATUS_INFORM: "draft.status.sync.inform",
DRAFT_INDEX_ADVANCE_REQUEST: "draft.index.advance.request",
DRAFT_INDEX_ADVANCE_CONFIRM: "draft.index.advance.confirm",
ORDER_DETERMINE_REQUEST: "order.determine.request",
@@ -22,6 +22,7 @@ export const DraftMessage = {
BID_START_INFORM: "bid.start.inform",
BID_START_REQUEST: "bid.start.request",
BID_PLACE_REQUEST: "bid.place.request",
+ BID_PLACE_CONFIRM: "bid.update.confirm",
BID_UPDATE_INFORM: "bid.update.inform",
BID_END_INFORM: "bid.end.inform",
NOMINATION_SUBMIT_REQUEST: "nomination.submit.request",
diff --git a/frontend/src/apps/draft/common/utils.js b/frontend/src/apps/draft/utils.js
similarity index 60%
rename from frontend/src/apps/draft/common/utils.js
rename to frontend/src/apps/draft/utils.js
index 02ad344..13caa00 100644
--- a/frontend/src/apps/draft/common/utils.js
+++ b/frontend/src/apps/draft/utils.js
@@ -1,4 +1,4 @@
-import { DraftMessage } from "../constants";
+import { DraftMessage } from "./constants";
export async function fetchDraftDetails(draftSessionId) {
return fetch(`/api/draft/${draftSessionId}/`)
@@ -37,38 +37,12 @@ export function isEmptyObject(obj) {
export const handleDraftStatusMessages = (event, setDraftState) => {
const message = JSON.parse(event.data);
const { type, payload } = message;
- console.log("Message: ", type, event?.data);
if (!payload) return;
- const {
- connected_participants,
- phase,
- draft_order,
- draft_index,
- current_movie,
- bidding_timer_end,
- bidding_timer_start,
- current_pick,
- next_picks,
- bids
- } = payload;
- if (type == DraftMessage.STATUS_SYNC_INFORM) {
+ if (type == DraftMessage.DRAFT_STATUS_INFORM) {
setDraftState(payload);
}
-
- setDraftState((prev) => ({
- ...prev,
- ...(connected_participants ? { connected_participants } : {}),
- ...(draft_order ? { draft_order } : {}),
- ...(draft_index ? { draft_index } : {}),
- ...(phase ? { phase: Number(phase) } : {}),
- ...(current_movie ? { current_movie } : {}),
- ...(bidding_timer_end ? { bidding_timer_end: Number(bidding_timer_end) } : {}),
- ...(current_pick ? { current_pick } : {}),
- ...(next_picks ? { next_picks } : {}),
- ...(bids ? {bids} : {})
- }));
};
export const handleUserIdentifyMessages = (event, setUser) => {
diff --git a/frontend/src/index.js b/frontend/src/index.js
index bef5fbf..4b06521 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -2,13 +2,13 @@ import './scss/styles.scss'
import React from "react";
import { createRoot } from "react-dom/client";
-import { WebSocketProvider } from "./apps/draft/common/WebSocketContext.jsx";
-import { DraftAdmin } from "./apps/draft/admin/DraftAdmin.jsx";
-import { DraftParticipant} from './apps/draft/participant/DraftParticipant.jsx'
+import { WebSocketProvider } from "./apps/draft/components/WebSocketContext.jsx";
+import { DraftAdmin } from "./apps/draft/DraftAdminBar.jsx";
+import { DraftParticipant} from './apps/draft/DraftDashboard.jsx'
import { DraftDebug} from './apps/draft/DraftDebug.jsx'
-const draftAdminRoot = document.getElementById("draft-admin-root");
+const draftAdminBarRoot = document.getElementById("draft-admin-bar-root");
const draftPartipantRoot = document.getElementById("draft-participant-root")
const draftDebugRoot = document.getElementById("draft-debug-root")
const {draftSessionId} = window; // from backend template
@@ -21,9 +21,9 @@ if (draftPartipantRoot) {
);
}
-if (draftAdminRoot) {
+if (draftAdminBarRoot) {
const wsUrl = `ws://${window.location.host}/ws/draft/session/${draftSessionId}/admin`;
- createRoot(draftAdminRoot).render(
+ createRoot(draftAdminBarRoot).render(
diff --git a/frontend/src/scss/styles.scss b/frontend/src/scss/styles.scss
index 8799767..505cf95 100644
--- a/frontend/src/scss/styles.scss
+++ b/frontend/src/scss/styles.scss
@@ -1,6 +1,6 @@
@use "../../node_modules/bootstrap/scss/bootstrap.scss";
@use "./fonts/graphique.css";
-@import url("https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Oswald:wght@200..700&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=League+Gothic&family=Oswald:wght@200..700&display=swap');
// Import only functions & variables
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@@ -127,8 +127,21 @@
}
}
-#draft-participant-root,
-#draft-admin-root {
+#draft-admin-bar {
+ @extend .d-flex;
+ @extend .flex-column;
+ @extend .border-top;
+ @extend .border-bottom;
+ @extend .gap-2;
+ @extend .p-2;
+ @extend .shadow-sm;
+ div {
+ @extend .d-flex;
+ @extend .justify-content-center
+ }
+}
+
+#draft-participant-root {
@extend .flex-grow-1;
.wrapper:first-child {
@extend .p-2;
@@ -137,44 +150,61 @@
gap: 1rem; /* space between panels */
justify-content: center; /* center the panels horizontally */
+ section {
+ max-width: 450px; /* never go beyond this */
+ min-width: 300px; /* keeps them from getting too small */
+ flex: 1 1 350px; /* grow/shrink, base width */
+ }
.panel {
@extend .border;
@extend .shadow-sm;
@extend .rounded-2;
- flex: 1 1 350px; /* grow/shrink, base width */
- max-width: 450px; /* never go beyond this */
- min-width: 300px; /* keeps them from getting too small */
header.panel-header {
@extend .p-1;
@extend .text-uppercase;
@extend .align-items-center;
@extend .border-bottom;
- @extend .border-secondary;
- background-color: $blue-100;
+ @extend .border-2;
+ @extend .border-secondary-subtle;
+ // background-color: $blue-100;
+ @extend .bg-dark;
+ @extend .bg-gradient;
+ @extend .text-light;
@extend .rounded-top-2;
.panel-title {
+ @extend .ms-2;
@extend .fw-bold;
@extend .fs-5;
}
}
}
- .panel.draft-live {
+ .bids-container {
+ overflow: scroll;
+ height: 85px;
+ }
+ #draft-live {
header.panel-header {
@extend .d-flex;
@extend .justify-content-between;
}
- .draft-live-state-container {
- @extend .d-flex;
- background-color: $green-100;
+ #draft-clock {
+ @extend .row;
+ @extend .g-0;
+ // background-color: $green-100;
+ @extend .text-light;
+ @extend .text-bg-dark;
+ @extend .lh-1;
.countdown-clock {
- @extend .fs-1;
- @extend .fw-bold;
+ font-family: 'League Gothic';
+ font-size: $font-size-base * 5;
+ @extend .fw-bolder;
@extend .col;
@extend .align-content-center;
@extend .text-center;
}
.pick-description {
@extend .col;
+ @extend .align-content-center;
}
}
div:has(.pick-list), div:has(.bid-list){
diff --git a/scripts/generate_js_constants.py b/scripts/generate_js_constants.py
old mode 100644
new mode 100755