395 lines
19 KiB
Python
395 lines
19 KiB
Python
import logging
|
|
from asyncio import sleep
|
|
from functools import partial
|
|
from typing import Optional, Union, Literal
|
|
|
|
from from_root import from_root
|
|
from rio import Column, Component, event, TextStyle, Text, Row, Image, Spacer, ProgressCircle, Button, Checkbox, ThemeContextSwitcher, Link, Revealer, PointerEventListener, \
|
|
PointerEvent, Rectangle, Color, Popup, Dropdown
|
|
|
|
from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserService, TicketingService, TeamService
|
|
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
|
from src.ezgg_lan_manager.components.TournamentDetailsInfoRow import TournamentDetailsInfoRow
|
|
from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text
|
|
from src.ezgg_lan_manager.types.Team import Team, TeamStatus
|
|
from src.ezgg_lan_manager.types.Tournament import Tournament
|
|
from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, tournament_status_to_display_text, tournament_format_to_display_texts, ParticipantType
|
|
from src.ezgg_lan_manager.types.User import User
|
|
from src.ezgg_lan_manager.types.UserSession import UserSession
|
|
|
|
logger = logging.getLogger(__name__.split(".")[-1])
|
|
|
|
|
|
class TournamentDetailsPage(Component):
|
|
tournament: Optional[Union[Tournament, str]] = None
|
|
rules_accepted: bool = False
|
|
user: Optional[User] = None
|
|
user_teams: list[Team] = []
|
|
loading: bool = False
|
|
participant_revealer_open: bool = False
|
|
current_tournament_user_or_team_list: Union[list[User], list[Team]] = []
|
|
team_dialog_open: bool = False
|
|
team_register_options: dict[str, Optional[Team]] = {"": None}
|
|
team_selected_for_register: Optional[Team] = None
|
|
|
|
# State for message above register button
|
|
message: str = ""
|
|
is_success: bool = False
|
|
|
|
@event.on_populate
|
|
async def on_populate(self) -> None:
|
|
try:
|
|
tournament_id = int(self.session.active_page_url.query_string.split("id=")[-1])
|
|
except (ValueError, AttributeError, TypeError):
|
|
tournament_id = None
|
|
if tournament_id is not None:
|
|
self.tournament = await self.session[TournamentService].get_tournament_by_id(tournament_id)
|
|
if self.tournament is not None:
|
|
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - {self.tournament.name}")
|
|
if self.tournament.participant_type == ParticipantType.PLAYER:
|
|
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
|
|
elif self.tournament.participant_type == ParticipantType.TEAM:
|
|
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_teams_from_participant_list(self.tournament.participants)
|
|
else:
|
|
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turniere")
|
|
|
|
try:
|
|
user_id = self.session[UserSession].user_id
|
|
self.user = await self.session[UserService].get_user(user_id)
|
|
self.user_teams = await self.session[TeamService].get_teams_for_user_by_id(user_id)
|
|
except KeyError:
|
|
self.user = None
|
|
self.user_teams = []
|
|
|
|
self.loading_done()
|
|
|
|
@staticmethod
|
|
async def artificial_delay() -> None:
|
|
await sleep(0.8) # https://medium.com/design-bootcamp/ux-psychology-of-artificial-waiting-enhancing-user-experiences-through-deliberate-delays-d7822faf3930
|
|
|
|
async def update(self) -> None:
|
|
self.tournament = await self.session[TournamentService].get_tournament_by_id(self.tournament.id)
|
|
if self.tournament is None:
|
|
return
|
|
if self.tournament.participant_type == ParticipantType.PLAYER:
|
|
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
|
|
elif self.tournament.participant_type == ParticipantType.TEAM:
|
|
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_teams_from_participant_list(self.tournament.participants)
|
|
|
|
def open_close_participant_revealer(self, _: PointerEvent) -> None:
|
|
self.participant_revealer_open = not self.participant_revealer_open
|
|
|
|
async def register_pressed(self) -> None:
|
|
self.loading = True
|
|
if not self.user:
|
|
return
|
|
|
|
user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)
|
|
if user_ticket is None:
|
|
self.is_success = False
|
|
self.message = "Turnieranmeldung nur mit Ticket"
|
|
else:
|
|
# Register single player
|
|
if self.tournament.participant_type == ParticipantType.PLAYER:
|
|
try:
|
|
await self.session[TournamentService].register_user_for_tournament(self.user.user_id, self.tournament.id)
|
|
await self.artificial_delay()
|
|
self.is_success = True
|
|
self.message = f"Erfolgreich angemeldet!"
|
|
except Exception as e:
|
|
logger.error(e)
|
|
self.is_success = False
|
|
self.message = f"Fehler: {e}"
|
|
# Register team
|
|
elif self.tournament.participant_type == ParticipantType.TEAM:
|
|
try:
|
|
team_register_options = {"": None}
|
|
for team in self.user_teams:
|
|
if team.members[self.user] == TeamStatus.OFFICER or team.members[self.user] == TeamStatus.LEADER:
|
|
team_register_options[team.name] = team
|
|
if team_register_options:
|
|
self.team_register_options = team_register_options
|
|
else:
|
|
self.team_register_options = {"": None}
|
|
except StopIteration as e:
|
|
logger.error(f"Error trying to teams to register: {e}")
|
|
self.team_dialog_open = True
|
|
return # Model should handle loading state now
|
|
else:
|
|
pass
|
|
await self.update()
|
|
self.loading = False
|
|
|
|
async def on_team_register_confirmed(self) -> None:
|
|
if self.team_selected_for_register is None:
|
|
await self.on_team_register_canceled()
|
|
return
|
|
try:
|
|
await self.session[TournamentService].register_team_for_tournament(self.team_selected_for_register.id, self.tournament.id)
|
|
await self.artificial_delay()
|
|
self.is_success = True
|
|
self.message = f"Erfolgreich angemeldet!"
|
|
self.team_dialog_open = False
|
|
self.team_selected_for_register = None
|
|
except Exception as e:
|
|
logger.error(e)
|
|
self.message = f"Fehler: {e}"
|
|
self.is_success = False
|
|
await self.update()
|
|
self.loading = False
|
|
|
|
async def on_team_register_canceled(self) -> None:
|
|
self.team_dialog_open = False
|
|
self.team_selected_for_register = None
|
|
self.loading = False
|
|
|
|
async def unregister_pressed(self, team: Optional[Team] = None) -> None:
|
|
self.loading = True
|
|
if not self.user:
|
|
return
|
|
|
|
try:
|
|
if self.tournament.participant_type == ParticipantType.PLAYER:
|
|
await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id)
|
|
elif self.tournament.participant_type == ParticipantType.TEAM:
|
|
if team.members[self.user] == TeamStatus.OFFICER or team.members[self.user] == TeamStatus.LEADER:
|
|
await self.session[TournamentService].unregister_team_from_tournament(team.id, self.tournament.id)
|
|
else:
|
|
raise PermissionError("Nur Leiter und Offiziere können das Team abmelden")
|
|
await self.artificial_delay()
|
|
self.is_success = True
|
|
self.message = f"Erfolgreich abgemeldet!"
|
|
except Exception as e:
|
|
self.is_success = False
|
|
self.message = f"Fehler: {e}"
|
|
await self.update()
|
|
self.loading = False
|
|
|
|
async def tree_button_clicked(self) -> None:
|
|
pass # ToDo: Implement tournament tree view
|
|
|
|
def loading_done(self) -> None:
|
|
if self.tournament is None:
|
|
self.tournament = "Turnier konnte nicht gefunden werden"
|
|
|
|
def build(self) -> Component:
|
|
if self.tournament is None:
|
|
content = Column(
|
|
ProgressCircle(
|
|
color="secondary",
|
|
align_x=0.5,
|
|
margin_top=0,
|
|
margin_bottom=0
|
|
),
|
|
min_height=10
|
|
)
|
|
elif isinstance(self.tournament, str):
|
|
content = Row(
|
|
Text(
|
|
text=self.tournament,
|
|
style=TextStyle(
|
|
fill=self.session.theme.background_color,
|
|
font_size=1
|
|
),
|
|
margin_top=2,
|
|
margin_bottom=2,
|
|
align_x=0.5
|
|
)
|
|
)
|
|
else:
|
|
tournament_status_color = self.session.theme.background_color
|
|
tree_button = Spacer(grow_x=False, grow_y=False)
|
|
if self.tournament.status == TournamentStatus.OPEN:
|
|
tournament_status_color = self.session.theme.success_color
|
|
elif self.tournament.status == TournamentStatus.CLOSED:
|
|
tournament_status_color = self.session.theme.danger_color
|
|
elif self.tournament.status == TournamentStatus.ONGOING or self.tournament.status == TournamentStatus.COMPLETED:
|
|
tournament_status_color = self.session.theme.warning_color
|
|
tree_button = Button(
|
|
content="Turnierbaum anzeigen",
|
|
shape="rectangle",
|
|
style="minor",
|
|
color="hud",
|
|
margin_left=4,
|
|
margin_right=4,
|
|
margin_top=1,
|
|
on_press=self.tree_button_clicked
|
|
)
|
|
|
|
ids_of_participants = [p.id for p in self.tournament.participants]
|
|
color_key: Literal["hud", "danger"] = "hud"
|
|
on_press_function = self.register_pressed
|
|
if self.tournament.participant_type == ParticipantType.PLAYER:
|
|
self.current_tournament_user_or_team_list: list[User] # IDE TypeHint
|
|
participant_names = "\n".join([u.user_name for u in self.current_tournament_user_or_team_list])
|
|
if self.user and self.user.user_id in ids_of_participants: # User already registered for tournament
|
|
button_text = "Abmelden"
|
|
button_sensitive_hook = True # User has already accepted the rules previously
|
|
color_key = "danger"
|
|
on_press_function = self.unregister_pressed
|
|
elif self.user and self.user.user_id not in ids_of_participants:
|
|
button_text = "Anmelden"
|
|
button_sensitive_hook = self.rules_accepted
|
|
else:
|
|
# This should NEVER happen
|
|
button_text = "Anmelden"
|
|
button_sensitive_hook = False
|
|
elif self.tournament.participant_type == ParticipantType.TEAM:
|
|
self.current_tournament_user_or_team_list: list[Team] # IDE TypeHint
|
|
participant_names = "\n".join([t.name for t in self.current_tournament_user_or_team_list])
|
|
user_team_registered = []
|
|
for team in self.user_teams:
|
|
if team.id in ids_of_participants:
|
|
user_team_registered.append(team)
|
|
if self.user and len(user_team_registered) > 0: # Any of the users teams already registered for tournament
|
|
button_text = f"{user_team_registered[0].abbreviation} abmelden"
|
|
button_sensitive_hook = True # User has already accepted the rules previously
|
|
color_key = "danger"
|
|
on_press_function = partial(self.unregister_pressed, user_team_registered[0])
|
|
elif self.user and len(user_team_registered) == 0:
|
|
button_text = "Anmelden"
|
|
button_sensitive_hook = self.rules_accepted
|
|
else:
|
|
# This should NEVER happen
|
|
button_text = "Anmelden"
|
|
button_sensitive_hook = False
|
|
else:
|
|
logger.fatal("Did someone add new values to ParticipantType ? ;)")
|
|
return Column()
|
|
|
|
|
|
if self.tournament.status != TournamentStatus.OPEN or self.tournament.is_full:
|
|
button_sensitive_hook = False # Override button controls if tournament is not open anymore or full
|
|
|
|
if self.user:
|
|
accept_rules_row = Row(
|
|
ThemeContextSwitcher(content=Checkbox(is_on=self.bind().rules_accepted, margin_left=4), color=self.session.theme.hud_color),
|
|
Text("Ich akzeptiere die ", margin_left=1, style=TextStyle(fill=self.session.theme.background_color, font_size=0.8), overflow="nowrap", justify="right"),
|
|
Link(Text("Turnierregeln", margin_right=4, style=TextStyle(fill=self.session.theme.background_color, font_size=0.8, italic=True), overflow="nowrap", justify="left"), "./tournament-rules", open_in_new_tab=True)
|
|
)
|
|
button = Button(
|
|
content=button_text,
|
|
shape="rectangle",
|
|
style="major",
|
|
color=color_key,
|
|
margin_left=2,
|
|
margin_right=2,
|
|
is_sensitive=button_sensitive_hook,
|
|
on_press=on_press_function,
|
|
is_loading=self.loading
|
|
)
|
|
else:
|
|
# No UI here if user not logged in
|
|
accept_rules_row, button = Spacer(), Spacer()
|
|
|
|
content = Column(
|
|
Row(
|
|
Image(image=from_root(f"src/ezgg_lan_manager/assets/img/games/{self.tournament.game_title.image_name}"), margin_right=1),
|
|
Text(
|
|
text=self.tournament.name,
|
|
style=TextStyle(
|
|
fill=self.session.theme.background_color,
|
|
font_size=1.2
|
|
),
|
|
margin_top=1,
|
|
margin_bottom=1,
|
|
align_x=0.5
|
|
),
|
|
margin_right=6,
|
|
margin_left=6
|
|
),
|
|
Spacer(min_height=1),
|
|
TournamentDetailsInfoRow("Status", tournament_status_to_display_text(self.tournament.status), value_color=tournament_status_color),
|
|
TournamentDetailsInfoRow("Startzeit", f"{weekday_to_display_text(self.tournament.start_time.weekday())}, {self.tournament.start_time.strftime('%H:%M')} Uhr"),
|
|
TournamentDetailsInfoRow("Format", tournament_format_to_display_texts(self.tournament.format)[0]),
|
|
TournamentDetailsInfoRow("Best of", tournament_format_to_display_texts(self.tournament.format)[1]),
|
|
PointerEventListener(
|
|
content=Rectangle(
|
|
content=TournamentDetailsInfoRow(
|
|
"Teilnehmer ▴" if self.participant_revealer_open else "Teilnehmer ▾",
|
|
f"{len(self.current_tournament_user_or_team_list)} / {self.tournament.max_participants}",
|
|
value_color=self.session.theme.danger_color if self.tournament.is_full else self.session.theme.background_color,
|
|
key_color=self.session.theme.secondary_color
|
|
),
|
|
fill=Color.TRANSPARENT,
|
|
cursor="pointer"
|
|
),
|
|
on_press=self.open_close_participant_revealer
|
|
),
|
|
Revealer(
|
|
header=None,
|
|
content=Text(
|
|
participant_names,
|
|
style=TextStyle(fill=self.session.theme.background_color)
|
|
),
|
|
is_open=self.participant_revealer_open,
|
|
margin_left=4,
|
|
margin_right=4
|
|
),
|
|
tree_button,
|
|
Row(
|
|
Text(
|
|
text="Info",
|
|
style=TextStyle(
|
|
fill=self.session.theme.background_color,
|
|
font_size=1.2
|
|
),
|
|
margin_top=1,
|
|
margin_bottom=1,
|
|
align_x=0.5
|
|
)
|
|
),
|
|
# FixMe: Use rio.Markdown with correct TextStyle instead to allow basic text formatting from DB-side.
|
|
Text(self.tournament.description, margin_left=2, margin_right=2, style=TextStyle(fill=self.session.theme.background_color, font_size=1), overflow="wrap"),
|
|
Spacer(min_height=2),
|
|
accept_rules_row,
|
|
Spacer(min_height=0.5),
|
|
Text(self.message, margin_left=2, margin_right=2, style=TextStyle(fill=self.session.theme.success_color if self.is_success else self.session.theme.danger_color, font_size=1), overflow="wrap", justify="center"),
|
|
Spacer(min_height=0.5),
|
|
button
|
|
)
|
|
|
|
if self.tournament and self.tournament.participant_type == ParticipantType.TEAM:
|
|
content = Popup(
|
|
anchor=content,
|
|
content=Rectangle(
|
|
content=Column(
|
|
Text("Welches Team anmelden?", style=TextStyle(fill=self.session.theme.background_color, font_size=1.2), justify="center", margin_bottom=1),
|
|
ThemeContextSwitcher(
|
|
content=Dropdown(
|
|
options=self.team_register_options,
|
|
min_width=20,
|
|
selected_value=self.bind().team_selected_for_register
|
|
),
|
|
color=self.session.theme.hud_color,
|
|
margin_bottom=1
|
|
),
|
|
Row(
|
|
Button(content="Abbrechen", shape="rectangle", grow_x=False, on_press=self.on_team_register_canceled),
|
|
Button(content="Anmelden", shape="rectangle", grow_x=False, on_press=self.on_team_register_confirmed),
|
|
spacing=1
|
|
),
|
|
margin=0.5
|
|
),
|
|
min_width=30,
|
|
min_height=4,
|
|
fill=self.session.theme.primary_color,
|
|
margin_top=3.5,
|
|
stroke_width=0.3,
|
|
stroke_color=self.session.theme.neutral_color,
|
|
),
|
|
is_open=self.team_dialog_open,
|
|
color="none"
|
|
)
|
|
|
|
return Column(
|
|
MainViewContentBox(
|
|
Column(
|
|
Spacer(min_height=1),
|
|
content,
|
|
Spacer(min_height=1)
|
|
)
|
|
),
|
|
align_y=0
|
|
)
|