diff --git a/README.md b/README.md index 60f6d00..9d70fae 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This repository contains the code for the EZGG LAN Manager. ### 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. @@ -43,3 +43,4 @@ FLUSH PRIVILEGES; ``` 3. Make sure to **NOT** use the default passwords! 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. diff --git a/VERSION b/VERSION index 341cf11..f477849 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 \ No newline at end of file +0.2.2 \ No newline at end of file diff --git a/src/EzggLanManager.py b/src/EzggLanManager.py index 0779eca..82d5146 100644 --- a/src/EzggLanManager.py +++ b/src/EzggLanManager.py @@ -171,6 +171,11 @@ if __name__ == "__main__": name="TournamentRulesPage", url_segment="tournament-rules", build=pages.TournamentRulesPage, + ), + ComponentPage( + name="ConwaysGameOfLife", + url_segment="conway", + build=pages.ConwayPage, ) ], theme=theme, diff --git a/src/ezgg_lan_manager/assets/img/crackz.png b/src/ezgg_lan_manager/assets/img/crackz.png new file mode 100644 index 0000000..7be58c5 Binary files /dev/null and b/src/ezgg_lan_manager/assets/img/crackz.png differ diff --git a/src/ezgg_lan_manager/components/DesktopNavigation.py b/src/ezgg_lan_manager/components/DesktopNavigation.py index e828db7..20be550 100644 --- a/src/ezgg_lan_manager/components/DesktopNavigation.py +++ b/src/ezgg_lan_manager/components/DesktopNavigation.py @@ -5,6 +5,7 @@ from rio import * from src.ezgg_lan_manager import ConfigurationService, UserService, LocalDataService 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.services.LocalDataService import LocalData 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) user_navigation = [ DesktopNavigationButton("News", "./news"), - Spacer(min_height=1), + Spacer(min_height=0.7), DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"), DesktopNavigationButton("Ticket kaufen", "./buy_ticket"), DesktopNavigationButton("Sitzplan", "./seating"), @@ -53,12 +54,12 @@ class DesktopNavigation(Component): DesktopNavigationButton("Turniere", "./tournaments"), DesktopNavigationButton("FAQ", "./faq"), 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("Die EZ GG e.V.", "https://ezgg-ev.de/about", open_new_tab=True), DesktopNavigationButton("Kontakt", "./contact"), DesktopNavigationButton("Impressum & DSGVO", "./imprint"), - Spacer(min_height=1) + Spacer(min_height=0.7) ] team_navigation = [ 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("Catering", "./manage-catering", is_team_navigation=True), DesktopNavigationButton("Turniere", "./manage-tournaments", is_team_navigation=True), - Spacer(min_height=1), + Spacer(min_height=0.7), Revealer( header="Normale 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), user_info_and_login_box, *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 ), color=self.session.theme.neutral_color, diff --git a/src/ezgg_lan_manager/components/LoginBox.py b/src/ezgg_lan_manager/components/LoginBox.py index 496b3e7..6a83129 100644 --- a/src/ezgg_lan_manager/components/LoginBox.py +++ b/src/ezgg_lan_manager/components/LoginBox.py @@ -102,5 +102,5 @@ class LoginBox(Component): min_width=12, align_x=0.5, margin_top=0.3, - margin_bottom=2 - ) \ No newline at end of file + margin_bottom=1.5 + ) diff --git a/src/ezgg_lan_manager/components/NavigationSponsorBox.py b/src/ezgg_lan_manager/components/NavigationSponsorBox.py new file mode 100644 index 0000000..63cb354 --- /dev/null +++ b/src/ezgg_lan_manager/components/NavigationSponsorBox.py @@ -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 + ) diff --git a/src/ezgg_lan_manager/components/UserInfoBox.py b/src/ezgg_lan_manager/components/UserInfoBox.py index a1215c2..4db6d55 100644 --- a/src/ezgg_lan_manager/components/UserInfoBox.py +++ b/src/ezgg_lan_manager/components/UserInfoBox.py @@ -117,5 +117,5 @@ class UserInfoBox(Component): min_width=12, align_x=0.5, margin_top=0.3, - margin_bottom=2 + margin_bottom=1.5 ) diff --git a/src/ezgg_lan_manager/pages/ConwayPage.py b/src/ezgg_lan_manager/pages/ConwayPage.py new file mode 100644 index 0000000..fba7f3c --- /dev/null +++ b/src/ezgg_lan_manager/pages/ConwayPage.py @@ -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) diff --git a/src/ezgg_lan_manager/pages/TournamentDetailsPage.py b/src/ezgg_lan_manager/pages/TournamentDetailsPage.py index 042d907..e481904 100644 --- a/src/ezgg_lan_manager/pages/TournamentDetailsPage.py +++ b/src/ezgg_lan_manager/pages/TournamentDetailsPage.py @@ -1,14 +1,14 @@ +from asyncio import sleep 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 -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.TournamentDetailsInfoRow import TournamentDetailsInfoRow 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.Tournament import Tournament 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() + @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: self.participant_revealer_open = not self.participant_revealer_open @@ -53,15 +61,21 @@ class TournamentDetailsPage(Component): if not self.user: return - try: - await self.session[TournamentService].register_user_for_tournament(self.user.user_id, self.tournament.id) - self.is_success = True - self.message = f"Erfolgreich angemeldet!" - except Exception as e: + user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id) + if user_ticket is None: 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 - await self.on_populate() async def unregister_pressed(self) -> None: self.loading = True @@ -70,13 +84,14 @@ class TournamentDetailsPage(Component): try: await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id) + 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 - await self.on_populate() async def tree_button_clicked(self) -> None: pass # ToDo: Implement tournament tree view @@ -198,7 +213,7 @@ class TournamentDetailsPage(Component): content=Rectangle( content=TournamentDetailsInfoRow( "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, key_color=self.session.theme.secondary_color ), diff --git a/src/ezgg_lan_manager/pages/__init__.py b/src/ezgg_lan_manager/pages/__init__.py index d20bffc..8bb9e24 100644 --- a/src/ezgg_lan_manager/pages/__init__.py +++ b/src/ezgg_lan_manager/pages/__init__.py @@ -22,3 +22,4 @@ from .ManageTournamentsPage import ManageTournamentsPage from .OverviewPage import OverviewPage from .TournamentDetailsPage import TournamentDetailsPage from .TournamentRulesPage import TournamentRulesPage +from .ConwayPage import ConwayPage diff --git a/src/ezgg_lan_manager/services/MailingService.py b/src/ezgg_lan_manager/services/MailingService.py index 8636c68..cb3121f 100644 --- a/src/ezgg_lan_manager/services/MailingService.py +++ b/src/ezgg_lan_manager/services/MailingService.py @@ -45,7 +45,7 @@ class MailingService: return f""" 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.