From 1f9c1798c47d7622a0092c0304a0676699ebb39b Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 11 Jun 2024 21:48:34 +0200 Subject: [PATCH] Basic working diva frontend with name editing and lv string editing. Working on playlog page --- titles/diva/frontend.py | 138 +++++++++++++++- titles/diva/schema/profile.py | 2 +- titles/diva/schema/score.py | 20 +++ titles/diva/templates/css/diva_style.css | 195 +++++++++++++++++++++++ titles/diva/templates/diva_header.jinja | 17 ++ titles/diva/templates/diva_index.jinja | 92 ++++++++++- titles/diva/templates/diva_playlog.jinja | 114 +++++++++++++ 7 files changed, 569 insertions(+), 9 deletions(-) create mode 100644 titles/diva/templates/diva_header.jinja create mode 100644 titles/diva/templates/diva_playlog.jinja diff --git a/titles/diva/frontend.py b/titles/diva/frontend.py index ca93ca0..2015c22 100644 --- a/titles/diva/frontend.py +++ b/titles/diva/frontend.py @@ -27,7 +27,13 @@ class DivaFrontend(FE_Base): def get_routes(self) -> List[Route]: return [ - Route("/", self.render_GET, methods=['GET']) + Route("/", self.render_GET, methods=['GET']), + Mount("/playlog", routes=[ + Route("/", self.render_GET_playlog, methods=['GET']), + Route("/{index}", self.render_GET_playlog, methods=['GET']), + ]), + Route("/update.name", self.update_name, methods=['POST']), + Route("/update.lv", self.update_lv, methods=['POST']), ] async def render_GET(self, request: Request) -> bytes: @@ -39,14 +45,138 @@ class DivaFrontend(FE_Base): usr_sesh = UserSession() if usr_sesh.user_id > 0: - profile = self.data.profile + profile = await self.data.profile.get_profile(usr_sesh.user_id, 1) resp = Response(template.render( title=f"{self.core_config.server.name} | {self.nav_name}", game_list=self.environment.globals["game_list"], sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, profile=profile - )) + ), media_type="text/html; charset=utf-8") return resp else: - return RedirectResponse("/login") \ No newline at end of file + return RedirectResponse("/login") + + async def render_GET_playlog(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/diva/templates/diva_playlog.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + path_index = request.path_params.get("index") + if not path_index or int(path_index) < 1: + index = 0 + else: + index = int(path_index) - 1 # 0 and 1 are 1st page + user_id = usr_sesh.user_id + playlog_count = await self.data.score.get_user_playlogs_count(user_id) + if playlog_count < index * 20 : + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + score_count=0 + ), media_type="text/html; charset=utf-8") + playlog = await self.data.score.get_playlogs(user_id, index, 20) #Maybe change to the playlog instead of direct scores + playlog_with_title = [] + for record in playlog: + song = await self.data.static.get_music_chart(record[2], record[3], record[4]) + if song: + title = song[4] + artist = song[5] + else: + title = "Unknown" + artist = "Unknown" + playlog_with_title.append({ + "raw": record, + "title": title, + "artist": artist + }) + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, + playlog=playlog_with_title, + playlog_count=playlog_count + ), media_type="text/html; charset=utf-8") + else: + return RedirectResponse("/gate/", 300) + + async def update_name(self, request: Request) -> Response: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/login") + + form_data = await request.form() + new_name: str = form_data.get("new_name") + new_name_full = "" + + if not new_name: + return RedirectResponse("/gate/?e=4", 303) + + if len(new_name) > 8: + return RedirectResponse("/gate/?e=8", 303) + + for x in new_name: # FIXME: This will let some invalid characters through atm + o = ord(x) + try: + if o == 0x20: + new_name_full += chr(0x3000) + elif o < 0x7F and o > 0x20: + new_name_full += chr(o + 0xFEE0) + elif o <= 0x7F: + self.logger.warn(f"Invalid ascii character {o:02X}") + return RedirectResponse("/gate/?e=4", 303) + else: + new_name_full += x + + except Exception as e: + self.logger.error(f"Something went wrong parsing character {o:04X} - {e}") + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_profile(usr_sesh.user_id, player_name=new_name_full): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/diva", 303) + + async def update_lv(self, request: Request) -> Response: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/login") + + form_data = await request.form() + new_lv: str = form_data.get("new_lv") + new_lv_full = "" + + if not new_lv: + return RedirectResponse("/gate/?e=4", 303) + + if len(new_lv) > 8: + return RedirectResponse("/gate/?e=8", 303) + + for x in new_lv: # FIXME: This will let some invalid characters through atm + o = ord(x) + try: + if o == 0x20: + new_lv_full += chr(0x3000) + elif o < 0x7F and o > 0x20: + new_lv_full += chr(o + 0xFEE0) + elif o <= 0x7F: + self.logger.warn(f"Invalid ascii character {o:02X}") + return RedirectResponse("/gate/?e=4", 303) + else: + new_lv_full += x + + except Exception as e: + self.logger.error(f"Something went wrong parsing character {o:04X} - {e}") + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_profile(usr_sesh.user_id, lv_str=new_lv_full): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/diva", 303) diff --git a/titles/diva/schema/profile.py b/titles/diva/schema/profile.py index f3d00ae..06e20be 100644 --- a/titles/diva/schema/profile.py +++ b/titles/diva/schema/profile.py @@ -102,7 +102,7 @@ class DivaProfileData(BaseData): self.logger.error( f"update_profile: failed to update profile! profile: {aime_id}" ) - return None + return result async def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]: """ diff --git a/titles/diva/schema/score.py b/titles/diva/schema/score.py index e802a41..65ff99c 100644 --- a/titles/diva/schema/score.py +++ b/titles/diva/schema/score.py @@ -239,3 +239,23 @@ class DivaScoreData(BaseData): if result is None: return None return result.fetchall() + + async def get_playlogs(self, user_id: int, idx: int = 0, limit: int = 0) -> Optional[List[Row]]: + sql = playlog.select(playlog.c.user == user_id) + + if limit: + sql = sql.limit(limit) + if idx: + sql = sql.offset(idx) + + result = await self.execute(sql) + if result: + return result.fetchall() + + async def get_user_playlogs_count(self, aime_id: int) -> Optional[int]: + sql = select(func.count()).where(playlog.c.user == aime_id) + result = await self.execute(sql) + if result is None: + self.logger.warning(f"aimu_id {aime_id} has no scores ") + return None + return result.scalar() diff --git a/titles/diva/templates/css/diva_style.css b/titles/diva/templates/css/diva_style.css index e69de29..672db0f 100644 --- a/titles/diva/templates/css/diva_style.css +++ b/titles/diva/templates/css/diva_style.css @@ -0,0 +1,195 @@ +.diva-header { + text-align: center; +} + +ul.diva-navi { + list-style-type: none; + padding: 0; + overflow: hidden; + background-color: #333; + text-align: center; + display: inline-block; +} + +ul.diva-navi li { + display: inline-block; +} + +ul.diva-navi li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +ul.diva-navi li a:hover:not(.active) { + background-color: #111; +} + +ul.diva-navi li a.active { + background-color: #4CAF50; +} + +ul.diva-navi li.right { + float: right; +} + +@media screen and (max-width: 600px) { + + ul.diva-navi li.right, + ul.diva-navi li { + float: none; + display: block; + text-align: center; + } +} + +table { + border-collapse: collapse; + border-spacing: 0; + border-collapse: separate; + overflow: hidden; + background-color: #555555; + +} + +th, td { + text-align: left; + border: none; + +} + +th { + color: white; +} + +.table-rowdistinct tr:nth-child(even) { + background-color: #303030; +} + +.table-rowdistinct tr:nth-child(odd) { + background-color: #555555; +} + +caption { + text-align: center; + color: white; + font-size: 18px; + font-weight: bold; +} + +.table-large { + margin: 16px; +} + +.table-large th, +.table-large td { + padding: 8px; +} + +.table-small { + width: 100%; + margin: 4px; +} + +.table-small th, +.table-small td { + padding: 2px; +} + +.bg-card { + background-color: #555555; +} + +.card-hover { + transition: all 0.2s ease-in-out; +} + +.card-hover:hover { + transform: scale(1.02); +} + +.basic { + color: #28a745; + font-weight: bold; +} + +.hard { + color: #ffc107; + + font-weight: bold; +} + +.expert { + color: #dc3545; + font-weight: bold; +} + +.master { + color: #dd09e8; + font-weight: bold; +} + +.ultimate { + color: #000000; + font-weight: bold; +} + +.score { + color: #ffffff; + font-weight: bold; +} + +.rainbow { + background: linear-gradient(to right, red, yellow, lime, aqua, blue, fuchsia) 0 / 5em; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: bold; +} + +.platinum { + color: #FFFF00; + font-weight: bold; +} + +.gold { + color: #FFFF00; + font-weight: bold; +} + +.scrolling-text { + overflow: hidden; +} + +.scrolling-text p { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h6 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h5 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling { + animation: scroll 10s linear infinite; +} + +@keyframes scroll { + 0% { + transform: translateX(100%); + } + 100% { + transform: translateX(-100%); + } +} \ No newline at end of file diff --git a/titles/diva/templates/diva_header.jinja b/titles/diva/templates/diva_header.jinja new file mode 100644 index 0000000..b92379a --- /dev/null +++ b/titles/diva/templates/diva_header.jinja @@ -0,0 +1,17 @@ +
+

diva

+ +
+ \ No newline at end of file diff --git a/titles/diva/templates/diva_index.jinja b/titles/diva/templates/diva_index.jinja index aa698b4..0cba3f4 100644 --- a/titles/diva/templates/diva_index.jinja +++ b/titles/diva/templates/diva_index.jinja @@ -3,12 +3,96 @@ -
+ {% include 'titles/diva/templates/diva_header.jinja' %} + {% if profile is defined and profile is not none and profile|length > 0 %}
-
-

{{ title }}

-

{{ description }}

+
+
+ + + + + + + + + + + + + + + + + + + + + + +
OVERVIEW
Player name:{{ profile[3] }} + + Level string:{{ profile[4] }} + +
Lvl:{{ profile[5] }}
Lvl points:{{ profile[6] }}
Vocaloid points:{{ profile[7] }}
+
+
+
+ {% if error is defined %} + {% include "core/templates/widgets/err_banner.jinja" %} + {% endif %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No profile information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + diff --git a/titles/diva/templates/diva_playlog.jinja b/titles/diva/templates/diva_playlog.jinja new file mode 100644 index 0000000..cb63a00 --- /dev/null +++ b/titles/diva/templates/diva_playlog.jinja @@ -0,0 +1,114 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + +
+ {% include 'titles/diva/templates/diva_header.jinja' %} + {% if playlog is defined and playlog is not none %} +
+

Score counts: {{ playlog_count }}

+ {% set difficultyName = ['easy', 'normal', 'hard', 'extreme', 'extra extreme'] %} + {% for record in playlog %} +
+
+
+
{{ record.title }}
+
+
{{ record.artist }}
+
+
+
{{ difficultyName[record.raw[4]] }}
+
+
+
+ {% endfor %} +
+ {% set playlog_pages = playlog_count // 20 + 1 %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No Score information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + + +{% endblock content %} \ No newline at end of file