Release 0.2.2 #43

Merged
Typhus merged 8 commits from main into prod 2026-02-11 22:36:14 +00:00
12 changed files with 152 additions and 21 deletions

View File

@ -14,7 +14,7 @@ This repository contains the code for the EZGG LAN Manager.
### Step 1: Preparing Database ### Step 1: Preparing Database
To prepare the database, apply the SQL file located in `sql/create_database.sql` to your database server. This is easily accomplished with the MYSQL Workbench, but it can be also done by pipeing the file into the mariadb-server executable. To prepare the database, apply the SQL file located in `sql/create_database.sql` followed by `sql/tournament_patch.sql` to your database server. This is easily accomplished with the MYSQL Workbench, but it can be also done by pipeing the file into the mariadb-server executable.
Optionally, you can now execute the script `create_demo_database_content.py`, found in `src/ezgg_lan_manager/helpers`. Be aware that it can be buggy sometimes, especially if you overwrite existing data. Optionally, you can now execute the script `create_demo_database_content.py`, found in `src/ezgg_lan_manager/helpers`. Be aware that it can be buggy sometimes, especially if you overwrite existing data.
@ -43,3 +43,4 @@ FLUSH PRIVILEGES;
``` ```
3. Make sure to **NOT** use the default passwords! 3. Make sure to **NOT** use the default passwords!
4. Apply the `create_database.sql` when starting the MariaDB container for the first time. 4. Apply the `create_database.sql` when starting the MariaDB container for the first time.
5. Apply the `tournament_patch.sql` when starting the MariaDB container for the first time.

View File

@ -1 +1 @@
0.2.0 0.2.2

View File

@ -171,6 +171,11 @@ if __name__ == "__main__":
name="TournamentRulesPage", name="TournamentRulesPage",
url_segment="tournament-rules", url_segment="tournament-rules",
build=pages.TournamentRulesPage, build=pages.TournamentRulesPage,
),
ComponentPage(
name="ConwaysGameOfLife",
url_segment="conway",
build=pages.ConwayPage,
) )
], ],
theme=theme, theme=theme,

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -5,6 +5,7 @@ from rio import *
from src.ezgg_lan_manager import ConfigurationService, UserService, LocalDataService from src.ezgg_lan_manager import ConfigurationService, UserService, LocalDataService
from src.ezgg_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton from src.ezgg_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton
from src.ezgg_lan_manager.components.NavigationSponsorBox import NavigationSponsorBox
from src.ezgg_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox from src.ezgg_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox
from src.ezgg_lan_manager.services.LocalDataService import LocalData from src.ezgg_lan_manager.services.LocalDataService import LocalData
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
@ -44,7 +45,7 @@ class DesktopNavigation(Component):
self.force_login_box_refresh.append(user_info_and_login_box.force_refresh) self.force_login_box_refresh.append(user_info_and_login_box.force_refresh)
user_navigation = [ user_navigation = [
DesktopNavigationButton("News", "./news"), DesktopNavigationButton("News", "./news"),
Spacer(min_height=1), Spacer(min_height=0.7),
DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"), DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"),
DesktopNavigationButton("Ticket kaufen", "./buy_ticket"), DesktopNavigationButton("Ticket kaufen", "./buy_ticket"),
DesktopNavigationButton("Sitzplan", "./seating"), DesktopNavigationButton("Sitzplan", "./seating"),
@ -53,12 +54,12 @@ class DesktopNavigation(Component):
DesktopNavigationButton("Turniere", "./tournaments"), DesktopNavigationButton("Turniere", "./tournaments"),
DesktopNavigationButton("FAQ", "./faq"), DesktopNavigationButton("FAQ", "./faq"),
DesktopNavigationButton("Regeln & AGB", "./rules-gtc"), DesktopNavigationButton("Regeln & AGB", "./rules-gtc"),
Spacer(min_height=1), Spacer(min_height=0.7),
DesktopNavigationButton("Discord", "https://discord.gg/8gTjg34yyH", open_new_tab=True), DesktopNavigationButton("Discord", "https://discord.gg/8gTjg34yyH", open_new_tab=True),
DesktopNavigationButton("Die EZ GG e.V.", "https://ezgg-ev.de/about", open_new_tab=True), DesktopNavigationButton("Die EZ GG e.V.", "https://ezgg-ev.de/about", open_new_tab=True),
DesktopNavigationButton("Kontakt", "./contact"), DesktopNavigationButton("Kontakt", "./contact"),
DesktopNavigationButton("Impressum & DSGVO", "./imprint"), DesktopNavigationButton("Impressum & DSGVO", "./imprint"),
Spacer(min_height=1) Spacer(min_height=0.7)
] ]
team_navigation = [ team_navigation = [
Text("Verwaltung", align_x=0.5, margin_top=0.3, style=TextStyle(fill=Color.from_hex("F0EADE"), font_size=1.2)), Text("Verwaltung", align_x=0.5, margin_top=0.3, style=TextStyle(fill=Color.from_hex("F0EADE"), font_size=1.2)),
@ -67,7 +68,7 @@ class DesktopNavigation(Component):
DesktopNavigationButton("Benutzer", "./manage-users", is_team_navigation=True), DesktopNavigationButton("Benutzer", "./manage-users", is_team_navigation=True),
DesktopNavigationButton("Catering", "./manage-catering", is_team_navigation=True), DesktopNavigationButton("Catering", "./manage-catering", is_team_navigation=True),
DesktopNavigationButton("Turniere", "./manage-tournaments", is_team_navigation=True), DesktopNavigationButton("Turniere", "./manage-tournaments", is_team_navigation=True),
Spacer(min_height=1), Spacer(min_height=0.7),
Revealer( Revealer(
header="Normale Navigation", header="Normale Navigation",
content=Column(*user_navigation), content=Column(*user_navigation),
@ -83,6 +84,8 @@ class DesktopNavigation(Component):
Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2), Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2),
user_info_and_login_box, user_info_and_login_box,
*nav_to_use, *nav_to_use,
Text("Unsere Sponsoren", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=0.9), margin_bottom=0.5, margin_top=1),
NavigationSponsorBox(img_name="crackz", url="https://www.crackz.gg/"),
align_y=0 align_y=0
), ),
color=self.session.theme.neutral_color, color=self.session.theme.neutral_color,

View File

@ -102,5 +102,5 @@ class LoginBox(Component):
min_width=12, min_width=12,
align_x=0.5, align_x=0.5,
margin_top=0.3, margin_top=0.3,
margin_bottom=2 margin_bottom=1.5
) )

View File

@ -0,0 +1,23 @@
from from_root import from_root
from rio import Component, Link, Rectangle, Image, Color
class NavigationSponsorBox(Component):
img_name: str
url: str
img_suffix: str = "png"
def build(self) -> Component:
return Link(
content=Rectangle(
content=Image(image=from_root(f"src/ezgg_lan_manager/assets/img/{self.img_name}.{self.img_suffix}"), min_width=10, min_height=10),
stroke_width=0.1,
stroke_color=Color.TRANSPARENT,
hover_stroke_width=0.1,
hover_stroke_color=self.session.theme.secondary_color,
margin=0.6,
cursor="pointer"
),
target_url=self.url,
open_in_new_tab=True
)

View File

@ -117,5 +117,5 @@ class UserInfoBox(Component):
min_width=12, min_width=12,
align_x=0.5, align_x=0.5,
margin_top=0.3, margin_top=0.3,
margin_bottom=2 margin_bottom=1.5
) )

View File

@ -0,0 +1,83 @@
from __future__ import annotations
from copy import deepcopy
from random import randint
from typing import * # type: ignore
from rio import Component, event, Column, Row, Color, Rectangle
class ConwayPage(Component):
"""
This is an Easter egg.
"""
active_generation: list[list] = []
rows: int = 36
cols: int = 20
@event.periodic(1)
async def calc_next_gen(self) -> None:
self.create_next_grid()
@event.on_populate
def prepare(self) -> None:
self.active_generation = self.create_initial_grid()
def create_initial_grid(self) -> list[list]:
grid = []
for row in range(self.rows):
grid_rows = []
for col in range(self.cols):
if randint(0, 7) == 0:
grid_rows += [1]
else:
grid_rows += [0]
grid += [grid_rows]
return grid
def create_next_grid(self) -> None:
next_grid = deepcopy(self.active_generation)
for row in range(self.rows):
for col in range(self.cols):
live_neighbors = self.get_live_neighbors(row, col, self.active_generation)
if live_neighbors < 2 or live_neighbors > 3:
next_grid[row][col] = 0
elif live_neighbors == 3 and self.active_generation[row][col] == 0:
next_grid[row][col] = 1
else:
next_grid[row][col] = self.active_generation[row][col]
self.active_generation = next_grid
def get_live_neighbors(self, row: int, col: int, grid: list[list]) -> int:
life_sum = 0
for i in range(-1, 2):
for j in range(-1, 2):
if not (i == 0 and j == 0):
life_sum += grid[((row + i) % self.rows)][((col + j) % self.cols)]
return life_sum
def grid_changing(self, next_grid: list[list]) -> bool:
for row in range(self.rows):
for col in range(self.cols):
if not self.active_generation[row][col] == next_grid[row][col]:
return True
return False
def build(self) -> Component:
rows = []
for row in self.active_generation:
rectangles = []
for cell in row:
color = Color.WHITE if cell == 1 else Color.BLACK
rectangles.append(Rectangle(fill=color, transition_time=0.3))
rows.append(Row(*rectangles))
return Column(*rows)

View File

@ -1,14 +1,14 @@
from asyncio import sleep
from typing import Optional, Union, Literal from typing import Optional, Union, Literal
from from_root import from_root from from_root import from_root
from rio import Column, Component, event, TextStyle, Text, Row, Image, Spacer, ProgressCircle, Button, Checkbox, ThemeContextSwitcher, Link, Revealer, PointerEventListener, \ from rio import Column, Component, event, TextStyle, Text, Row, Image, Spacer, ProgressCircle, Button, Checkbox, ThemeContextSwitcher, Link, Revealer, PointerEventListener, \
PointerEvent, Rectangle, Color PointerEvent, Rectangle, Color
from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserService from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserService, TicketingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TournamentDetailsInfoRow import TournamentDetailsInfoRow 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.DateUtil import weekday_to_display_text
from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Tournament import Tournament 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 from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, tournament_status_to_display_text, tournament_format_to_display_texts
@ -45,6 +45,14 @@ class TournamentDetailsPage(Component):
self.loading_done() 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)
self.current_tournament_user_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
def open_close_participant_revealer(self, _: PointerEvent) -> None: def open_close_participant_revealer(self, _: PointerEvent) -> None:
self.participant_revealer_open = not self.participant_revealer_open self.participant_revealer_open = not self.participant_revealer_open
@ -53,15 +61,21 @@ class TournamentDetailsPage(Component):
if not self.user: if not self.user:
return return
try: user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)
await self.session[TournamentService].register_user_for_tournament(self.user.user_id, self.tournament.id) if user_ticket is None:
self.is_success = True
self.message = f"Erfolgreich angemeldet!"
except Exception as e:
self.is_success = False self.is_success = False
self.message = f"Fehler: {e}" self.message = "Turnieranmeldung nur mit Ticket"
else:
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:
self.is_success = False
self.message = f"Fehler: {e}"
await self.update()
self.loading = False self.loading = False
await self.on_populate()
async def unregister_pressed(self) -> None: async def unregister_pressed(self) -> None:
self.loading = True self.loading = True
@ -70,13 +84,14 @@ class TournamentDetailsPage(Component):
try: try:
await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id) await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id)
await self.artificial_delay()
self.is_success = True self.is_success = True
self.message = f"Erfolgreich abgemeldet!" self.message = f"Erfolgreich abgemeldet!"
except Exception as e: except Exception as e:
self.is_success = False self.is_success = False
self.message = f"Fehler: {e}" self.message = f"Fehler: {e}"
await self.update()
self.loading = False self.loading = False
await self.on_populate()
async def tree_button_clicked(self) -> None: async def tree_button_clicked(self) -> None:
pass # ToDo: Implement tournament tree view pass # ToDo: Implement tournament tree view
@ -198,7 +213,7 @@ class TournamentDetailsPage(Component):
content=Rectangle( content=Rectangle(
content=TournamentDetailsInfoRow( content=TournamentDetailsInfoRow(
"Teilnehmer ▴" if self.participant_revealer_open else "Teilnehmer ▾", "Teilnehmer ▴" if self.participant_revealer_open else "Teilnehmer ▾",
f"{len(self.tournament.participants)} / {self.tournament.max_participants}", f"{len(self.current_tournament_user_list)} / {self.tournament.max_participants}",
value_color=self.session.theme.danger_color if self.tournament.is_full else self.session.theme.background_color, 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 key_color=self.session.theme.secondary_color
), ),

View File

@ -22,3 +22,4 @@ from .ManageTournamentsPage import ManageTournamentsPage
from .OverviewPage import OverviewPage from .OverviewPage import OverviewPage
from .TournamentDetailsPage import TournamentDetailsPage from .TournamentDetailsPage import TournamentDetailsPage
from .TournamentRulesPage import TournamentRulesPage from .TournamentRulesPage import TournamentRulesPage
from .ConwayPage import ConwayPage

View File

@ -45,7 +45,7 @@ class MailingService:
return f""" return f"""
Hallo {user.user_name}, Hallo {user.user_name},
deinem Account wurden {added_balance} hinzugefügt. Dein neues Guthaben beträgt nun {total_balance} . deinem Account wurden {added_balance:.2f} hinzugefügt. Dein neues Guthaben beträgt nun {total_balance:.2f} .
Wenn du zu dieser Aufladung Fragen hast, stehen wir dir in unserem Discord Server oder per Mail an {self._configuration_service.get_lan_info().organizer_mail} zur Verfügung. Wenn du zu dieser Aufladung Fragen hast, stehen wir dir in unserem Discord Server oder per Mail an {self._configuration_service.get_lan_info().organizer_mail} zur Verfügung.