migrate seaing plan

This commit is contained in:
David Rodenkirchen
2026-05-23 01:49:17 +02:00
parent 547601d7df
commit 5d422a9863
8 changed files with 495 additions and 2 deletions
+179
View File
@@ -0,0 +1,179 @@
from typing import Optional
from rio import Component, Rectangle, Grid, Column, Row, Text, TextStyle, Color, Spacer
from elm.components import SeatPixel, WallPixel, TextPixel
from elm.types import Seat
MAX_GRID_WIDTH_PIXELS = 70
MAX_GRID_HEIGHT_PIXELS = 60
class SeatingPlan(Component):
preloaded_seats: list[Seat]
def get_seat(self, seat_id: str) -> Optional[Seat]:
return next(filter(lambda x: x.seat_id == seat_id, self.preloaded_seats), None)
"""
This seating plan is for the community center "Donsbach"
"""
def build(self) -> Component:
grid = Grid()
# Outlines
for x in range(0, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=0, column=x)
for y in range(0, MAX_GRID_HEIGHT_PIXELS):
grid.add(WallPixel(), row=y, column=0)
for x in range(0, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=MAX_GRID_HEIGHT_PIXELS, column=x)
for x in range(0, 41):
grid.add(WallPixel(), row=15, column=x)
for x in range(51, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=32, column=x)
for x in range(41, 44):
grid.add(WallPixel(), row=32, column=x)
grid.add(WallPixel(), row=19, column=x)
for x in range(52, MAX_GRID_WIDTH_PIXELS):
grid.add(WallPixel(), row=11, column=x)
grid.add(WallPixel(), row=22, column=x)
for x in range(32, 40):
grid.add(WallPixel(), row=5, column=x)
grid.add(WallPixel(), row=10, column=x)
for y in range(5, 11):
grid.add(WallPixel(), row=y, column=31)
grid.add(WallPixel(), row=y, column=40)
for y in range(40, MAX_GRID_HEIGHT_PIXELS):
grid.add(WallPixel(), row=y, column=40)
for y in range(32, 36):
grid.add(WallPixel(), row=y, column=40)
for y in range(19, 33):
grid.add(WallPixel(), row=y, column=44)
for y in range(16, 20):
grid.add(WallPixel(), row=y, column=40)
for y in range(0, 5):
grid.add(WallPixel(), row=y, column=51)
for y in range(9, 15):
grid.add(WallPixel(), row=y, column=51)
for y in range(19, 33):
grid.add(WallPixel(), row=y, column=51)
# Block A
grid.add(SeatPixel("A01", seat=self.get_seat("A01"), seat_orientation="bottom"), row=57, column=1, width=5, height=2)
grid.add(SeatPixel("A02", seat=self.get_seat("A02"), seat_orientation="bottom"), row=57, column=6, width=5, height=2)
grid.add(SeatPixel("A03", seat=self.get_seat("A03"), seat_orientation="bottom"), row=57, column=11, width=5, height=2)
grid.add(SeatPixel("A04", seat=self.get_seat("A04"), seat_orientation="bottom"), row=57, column=16, width=5, height=2)
grid.add(SeatPixel("A05", seat=self.get_seat("A05"), seat_orientation="bottom"), row=57, column=21, width=5, height=2)
grid.add(SeatPixel("A10", seat=self.get_seat("A10"), seat_orientation="top"), row=55, column=1, width=5, height=2)
grid.add(SeatPixel("A11", seat=self.get_seat("A11"), seat_orientation="top"), row=55, column=6, width=5, height=2)
grid.add(SeatPixel("A12", seat=self.get_seat("A12"), seat_orientation="top"), row=55, column=11, width=5, height=2)
grid.add(SeatPixel("A13", seat=self.get_seat("A13"), seat_orientation="top"), row=55, column=16, width=5, height=2)
grid.add(SeatPixel("A14", seat=self.get_seat("A14"), seat_orientation="top"), row=55, column=21, width=5, height=2)
# Block B
grid.add(SeatPixel("B01", seat=self.get_seat("B01"), seat_orientation="bottom"), row=50, column=1, width=3, height=2)
grid.add(SeatPixel("B02", seat=self.get_seat("B02"), seat_orientation="bottom"), row=50, column=4, width=3, height=2)
grid.add(SeatPixel("B03", seat=self.get_seat("B03"), seat_orientation="bottom"), row=50, column=7, width=3, height=2)
grid.add(SeatPixel("B04", seat=self.get_seat("B04"), seat_orientation="bottom"), row=50, column=10, width=3, height=2)
grid.add(SeatPixel("B05", seat=self.get_seat("B05"), seat_orientation="bottom"), row=50, column=13, width=3, height=2)
grid.add(SeatPixel("B06", seat=self.get_seat("B06"), seat_orientation="bottom"), row=50, column=16, width=3, height=2)
grid.add(SeatPixel("B07", seat=self.get_seat("B07"), seat_orientation="bottom"), row=50, column=19, width=3, height=2)
grid.add(SeatPixel("B08", seat=self.get_seat("B08"), seat_orientation="bottom"), row=50, column=22, width=3, height=2)
grid.add(SeatPixel("B10", seat=self.get_seat("B10"), seat_orientation="top"), row=48, column=1, width=3, height=2)
grid.add(SeatPixel("B11", seat=self.get_seat("B11"), seat_orientation="top"), row=48, column=4, width=3, height=2)
grid.add(SeatPixel("B12", seat=self.get_seat("B12"), seat_orientation="top"), row=48, column=7, width=3, height=2)
grid.add(SeatPixel("B13", seat=self.get_seat("B13"), seat_orientation="top"), row=48, column=10, width=3, height=2)
grid.add(SeatPixel("B14", seat=self.get_seat("B14"), seat_orientation="top"), row=48, column=13, width=3, height=2)
grid.add(SeatPixel("B15", seat=self.get_seat("B15"), seat_orientation="top"), row=48, column=16, width=3, height=2)
grid.add(SeatPixel("B16", seat=self.get_seat("B16"), seat_orientation="top"), row=48, column=19, width=3, height=2)
grid.add(SeatPixel("B17", seat=self.get_seat("B17"), seat_orientation="top"), row=48, column=22, width=3, height=2)
# Block C
grid.add(SeatPixel("C01", seat=self.get_seat("C01"), seat_orientation="bottom"), row=43, column=1, width=3, height=2)
grid.add(SeatPixel("C02", seat=self.get_seat("C02"), seat_orientation="bottom"), row=43, column=4, width=3, height=2)
grid.add(SeatPixel("C03", seat=self.get_seat("C03"), seat_orientation="bottom"), row=43, column=7, width=3, height=2)
grid.add(SeatPixel("C04", seat=self.get_seat("C04"), seat_orientation="bottom"), row=43, column=10, width=3, height=2)
grid.add(SeatPixel("C05", seat=self.get_seat("C05"), seat_orientation="bottom"), row=43, column=13, width=3, height=2)
grid.add(SeatPixel("C06", seat=self.get_seat("C06"), seat_orientation="bottom"), row=43, column=16, width=3, height=2)
grid.add(SeatPixel("C07", seat=self.get_seat("C07"), seat_orientation="bottom"), row=43, column=19, width=3, height=2)
grid.add(SeatPixel("C08", seat=self.get_seat("C08"), seat_orientation="bottom"), row=43, column=22, width=3, height=2)
grid.add(SeatPixel("C10", seat=self.get_seat("C10"), seat_orientation="top"), row=41, column=1, width=3, height=2)
grid.add(SeatPixel("C11", seat=self.get_seat("C11"), seat_orientation="top"), row=41, column=4, width=3, height=2)
grid.add(SeatPixel("C12", seat=self.get_seat("C12"), seat_orientation="top"), row=41, column=7, width=3, height=2)
grid.add(SeatPixel("C13", seat=self.get_seat("C13"), seat_orientation="top"), row=41, column=10, width=3, height=2)
grid.add(SeatPixel("C14", seat=self.get_seat("C14"), seat_orientation="top"), row=41, column=13, width=3, height=2)
grid.add(SeatPixel("C15", seat=self.get_seat("C15"), seat_orientation="top"), row=41, column=16, width=3, height=2)
grid.add(SeatPixel("C16", seat=self.get_seat("C16"), seat_orientation="top"), row=41, column=19, width=3, height=2)
grid.add(SeatPixel("C17", seat=self.get_seat("C17"), seat_orientation="top"), row=41, column=22, width=3, height=2)
# Block D
grid.add(SeatPixel("D01", seat=self.get_seat("D01"), seat_orientation="bottom"), row=34, column=1, width=5, height=2)
grid.add(SeatPixel("D02", seat=self.get_seat("D02"), seat_orientation="bottom"), row=34, column=6, width=5, height=2)
grid.add(SeatPixel("D03", seat=self.get_seat("D03"), seat_orientation="bottom"), row=34, column=11, width=5, height=2)
grid.add(SeatPixel("D04", seat=self.get_seat("D04"), seat_orientation="bottom"), row=34, column=16, width=5, height=2)
grid.add(SeatPixel("D05", seat=self.get_seat("D05"), seat_orientation="bottom"), row=34, column=21, width=5, height=2)
grid.add(SeatPixel("D10", seat=self.get_seat("D10"), seat_orientation="top"), row=32, column=1, width=5, height=2)
grid.add(SeatPixel("D11", seat=self.get_seat("D11"), seat_orientation="top"), row=32, column=6, width=5, height=2)
grid.add(SeatPixel("D12", seat=self.get_seat("D12"), seat_orientation="top"), row=32, column=11, width=5, height=2)
grid.add(SeatPixel("D13", seat=self.get_seat("D13"), seat_orientation="top"), row=32, column=16, width=5, height=2)
grid.add(SeatPixel("D14", seat=self.get_seat("D14"), seat_orientation="top"), row=32, column=21, width=5, height=2)
# Block E
grid.add(SeatPixel("E01", seat=self.get_seat("E01"), seat_orientation="bottom"), row=27, column=1, width=5, height=2)
grid.add(SeatPixel("E02", seat=self.get_seat("E02"), seat_orientation="bottom"), row=27, column=6, width=5, height=2)
grid.add(SeatPixel("E03", seat=self.get_seat("E03"), seat_orientation="bottom"), row=27, column=11, width=5, height=2)
grid.add(SeatPixel("E04", seat=self.get_seat("E04"), seat_orientation="bottom"), row=27, column=16, width=5, height=2)
grid.add(SeatPixel("E05", seat=self.get_seat("E05"), seat_orientation="bottom"), row=27, column=21, width=5, height=2)
grid.add(SeatPixel("E10", seat=self.get_seat("E10"), seat_orientation="top"), row=25, column=1, width=5, height=2)
grid.add(SeatPixel("E11", seat=self.get_seat("E11"), seat_orientation="top"), row=25, column=6, width=5, height=2)
grid.add(SeatPixel("E12", seat=self.get_seat("E12"), seat_orientation="top"), row=25, column=11, width=5, height=2)
grid.add(SeatPixel("E13", seat=self.get_seat("E13"), seat_orientation="top"), row=25, column=16, width=5, height=2)
grid.add(SeatPixel("E14", seat=self.get_seat("E14"), seat_orientation="top"), row=25, column=21, width=5, height=2)
# Orga Block
grid.add(SeatPixel("Z01", seat=self.get_seat("Z01"), seat_orientation="top"), row=40, column=35, width=4, height=2)
grid.add(SeatPixel("Z02", seat=self.get_seat("Z02"), seat_orientation="top"), row=40, column=31, width=4, height=2)
grid.add(SeatPixel("Z\n0\n3", seat=self.get_seat("Z03"), seat_orientation="top"), row=40, column=29, width=2, height=6)
grid.add(SeatPixel("Z\n0\n4", seat=self.get_seat("Z04"), seat_orientation="bottom"), row=46, column=29, width=2, height=6)
grid.add(SeatPixel("Z05", seat=self.get_seat("Z05"), seat_orientation="bottom"), row=50, column=31, width=4, height=2)
grid.add(SeatPixel("Z06", seat=self.get_seat("Z06"), seat_orientation="bottom"), row=50, column=35, width=5, height=2)
# Stage
grid.add(TextPixel(text="Bühne"), row=16, column=1, width=39, height=4)
# Drinks
grid.add(TextPixel(text="G\ne\nt\nr\nä\nn\nk\ne"), row=20, column=40, width=4, height=12)
# Main Entrance
grid.add(TextPixel(text="H\na\nl\nl\ne\nn\n\nE\ni\nn\ng\na\nn\ng"), row=33, column=66, width=4, height=27)
# Sleeping
grid.add(TextPixel(icon_name="material/bed"), row=1, column=1, width=30, height=14)
# Toilet
grid.add(TextPixel(icon_name="material/wc"), row=1, column=52, width=19, height=10)
grid.add(TextPixel(icon_name="material/wc"), row=12, column=52, width=19, height=10)
return grid
+138
View File
@@ -0,0 +1,138 @@
from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, PointerEventListener, Row, PointerEvent, Tooltip
from typing import Optional, Literal
from rio.event import on_populate
from elm.types import Seat, UserSession, User
class SeatPixel(Component):
seat_id: str
seat_orientation: Literal["top", "bottom"]
seat: Optional[Seat] = None
associated_user: Optional[User] = None
@on_populate
async def on_populate(self) -> None:
if self.seat is None:
self.seat = await Seat.find_one(Seat.seat_id == self.seat_id.replace("\n", ""), fetch_links=True)
if self.seat and self.seat.user is not None:
self.associated_user = await self.seat.user.fetch()
async def on_press(self, _: PointerEvent) -> None:
self.session.navigate_to(f"./seat-info?seat_id={self.seat_id.replace("\n", "")}")
def determine_color(self) -> Color:
if self.seat is not None:
try:
user_name = self.session[UserSession].user_name
except KeyError:
user_name = None
if self.seat.user is not None and self.associated_user and self.associated_user.user_name == user_name:
return Color.from_hex("800080")
elif self.seat.is_blocked or self.seat.user is not None:
return self.session.theme.danger_color
return self.session.theme.success_color
return self.session.theme.success_color
def build(self) -> Component:
if self.seat is None:
return Spacer()
text = Text(f"{self.seat_id}", style=TextStyle(fill=Color.BLACK, font_size=0.9), align_x=0.5, selectable=False)
rec = Rectangle(
content=Row(text),
min_width=1,
min_height=1,
fill=self.determine_color(),
stroke_width=0.1,
hover_stroke_width=0.1,
stroke_color=Color.BLACK,
grow_x=True,
grow_y=True,
hover_fill=self.session.theme.hud_color,
transition_time=0.4,
ripple=True
)
if self.associated_user or self.seat.is_blocked:
return PointerEventListener(
content=Tooltip(
anchor=rec,
tip=self.associated_user.user_name if self.associated_user else "Gesperrt",
position=self.seat_orientation,
),
on_press=self.on_press,
)
else:
return PointerEventListener(
content=rec,
on_press=self.on_press,
)
class TextPixel(Component):
text: Optional[str] = None
icon_name: Optional[str] = None
no_outline: bool = False
def build(self) -> Component:
if self.text is not None:
content = Text(self.text, style=TextStyle(fill=self.session.theme.text_color, font_size=1), align_x=0.5, selectable=False)
elif self.icon_name is not None:
content = Icon(self.icon_name, fill=self.session.theme.text_color)
else:
content = None
return Rectangle(
content=content,
min_width=1,
min_height=1,
fill=self.session.theme.background_color,
stroke_width=0.0 if self.no_outline else 0.1,
stroke_color=self.session.theme.neutral_color,
hover_stroke_width=None if self.no_outline else 0.1,
grow_x=True,
grow_y=True,
hover_fill=None,
ripple=True
)
class WallPixel(Component):
def build(self) -> Component:
return Rectangle(
min_width=1,
min_height=1,
fill=Color.from_hex("434343"),
grow_x=True,
grow_y=True,
)
class DebugPixel(Component):
def build(self) -> Component:
return Rectangle(
content=Spacer(),
min_width=1,
min_height=1,
fill=self.session.theme.success_color,
hover_stroke_color=self.session.theme.hud_color,
hover_stroke_width=0.1,
grow_x=True,
grow_y=True,
hover_fill=self.session.theme.secondary_color,
transition_time=0.1
)
class InvisiblePixel(Component):
def build(self) -> Component:
return Rectangle(
content=Spacer(),
min_width=1,
min_height=1,
fill=Color.TRANSPARENT,
hover_stroke_width=0.0,
grow_x=True,
grow_y=True
)
+2
View File
@@ -7,3 +7,5 @@ from .AvatarEditBox import AvatarEditBox
from .AccountInfoBox import AccountInfoBox from .AccountInfoBox import AccountInfoBox
from .PersonalInfoBox import PersonalInfoBox from .PersonalInfoBox import PersonalInfoBox
from .BuyTicketBox import BuyTicketBox from .BuyTicketBox import BuyTicketBox
from .SeatingPlanPixels import *
from .SeatingPlan import *
+129
View File
@@ -0,0 +1,129 @@
from __future__ import annotations
from typing import Optional
from bson import ObjectId
from rio import Component, page, Rectangle, ProgressCircle, Row, QueryParameter, Column, Text, Spacer
from rio.event import on_populate
from elm import UserSession
from elm.components import ElmButton
from elm.types import Seat, User, Ticket
@page(name="Seat Info", url_segment="seat-info")
class SeatInfoPage(Component):
seat_id: QueryParameter[str] = ""
seat: Optional[Seat] = None
seat_user: Optional[User] = None
initial_load_done: bool = False
choosing_button_loading: bool = False
message: str = ""
message_is_error: bool = False
@on_populate
async def on_populate(self) -> None:
self.seat = await Seat.find_one(Seat.seat_id == self.seat_id)
if self.seat and self.seat.user is not None:
self.seat_user = await self.seat.user.fetch()
self.initial_load_done = True
async def choose_seat(self) -> None:
self.choosing_button_loading = True
try:
user_name = self.session[UserSession].user_name
except KeyError:
self.session.navigate_to("./login")
return
user = await User.find_one(User.user_name == user_name)
if not user:
return
user_ticket = await Ticket.find_one(
{"owner.$id": ObjectId(user.id)}
)
if not user_ticket or user_ticket.category != self.seat.category:
self.message = "Du hast nicht das passende Ticket"
self.message_is_error = True
self.choosing_button_loading = False
return
user_seat = await Seat.find_one(
{"user.$id": ObjectId(user.id)}
)
if user_seat is not None:
self.message = "Du hast bereits einen Sitzplatz"
self.message_is_error = True
self.choosing_button_loading = False
return
s = await Seat.find_one(Seat.seat_id == self.seat_id)
if not s:
return
s.user = user
await s.save()
self.message = "Sitzplatz gewählt!"
self.message_is_error = False
self.choosing_button_loading = False
def build(self) -> Component:
if not self.initial_load_done:
box_contents = [ProgressCircle(margin=1)]
else:
if self.seat is None:
box_contents = [Text(text="Der angeforderte Sitzplatz konnte nicht gefunden werden", margin=1, overflow="wrap", justify="center", fill=self.session.theme.danger_color)]
else:
box_contents = [
Row(
Text(text="Kategorie:", justify="left"),
Text(text=self.seat.category, justify="right"),
spacing=1
),
Row(
Text(text="Belegt:", justify="left"),
Text(text="Ja" if self.seat.user is not None or self.seat.is_blocked else "Nein", justify="right", fill=self.session.theme.danger_color if self.seat.user is not None or self.seat.is_blocked else self.session.theme.success_color),
spacing=1
),
Row(
Text(text="Nutzer:", justify="left"),
Text(text=self.seat_user.user_name if self.seat_user else "-", justify="right"),
spacing=1
),
]
if not self.seat.is_blocked and self.seat.user is None:
box_contents.append(
ElmButton(text="Platz wählen", on_press=self.choose_seat, is_loading=self.choosing_button_loading)
)
box_contents.append(
Text(text=self.message, fill=self.session.theme.danger_color if self.message_is_error else self.session.theme.success_color, overflow="wrap", justify="center")
)
return Row(
Rectangle(
content=Column(
Rectangle(
content=Rectangle(
content=Text(f"Sitzplatz: {self.seat_id}", margin=0.5, selectable=False),
fill=self.session.theme.header_box_background_color,
margin=0.4
),
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
),
Column(
*box_contents,
margin=1,
spacing=1
),
Spacer()
),
fill=self.session.theme.box_color,
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color
),
align_x=0.5,
align_y=0.5
)
+29
View File
@@ -0,0 +1,29 @@
from __future__ import annotations
from typing import Optional
from rio import Component, page, Rectangle, PointerEvent, ProgressCircle, Row
from rio.event import on_populate
from elm.components import SeatingPlan
from elm.types import Seat
@page(name="Seating Plan", url_segment="seating")
class SeatingPlanPage(Component):
preloaded_seats: Optional[list[Seat]] = None
@on_populate
async def on_populate(self) -> None:
self.preloaded_seats = await Seat.find_all().to_list()
def build(self) -> Component:
return Rectangle(
content=Row(ProgressCircle(), margin=self.session.screen_width // 6) if self.preloaded_seats is None else SeatingPlan(margin=0, preloaded_seats=self.preloaded_seats),
fill=self.session.theme.box_color,
stroke_width = 0.1,
stroke_color = self.session.theme.box_border_color,
margin_left=1,
margin_top=1
)
+2 -2
View File
@@ -4,7 +4,7 @@ from beanie import init_beanie
from pymongo import AsyncMongoClient from pymongo import AsyncMongoClient
from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.collection import AsyncCollection
from elm.types import User, Transaction, Ticket from elm.types import User, Transaction, Ticket, Seat
from elm.types.ConfigurationTypes import DatabaseConfiguration from elm.types.ConfigurationTypes import DatabaseConfiguration
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
@@ -33,5 +33,5 @@ class DatabaseService:
self._users: AsyncCollection = self._database["users"] self._users: AsyncCollection = self._database["users"]
await init_beanie( await init_beanie(
database=self._database, database=self._database,
document_models=[User, Transaction, Ticket] document_models=[User, Transaction, Ticket, Seat]
) )
+15
View File
@@ -0,0 +1,15 @@
from typing import Optional
from beanie import Document, Link
from elm.types import User
class Seat(Document):
seat_id: str
is_blocked: bool
category: str
user: Optional[Link[User]] = None
class Settings:
name = "seats"
+1
View File
@@ -3,3 +3,4 @@ from .UserSession import UserSession
from .ConfigurationTypes import * from .ConfigurationTypes import *
from .Transaction import Transaction from .Transaction import Transaction
from .Ticket import Ticket, TicketState from .Ticket import Ticket, TicketState
from .Seat import Seat