Skip to content

Commit e0ba917

Browse files
committed
LevelUp - Leaderboard Dash Integration Overhaul
1 parent ccd2542 commit e0ba917

File tree

5 files changed

+530
-73
lines changed

5 files changed

+530
-73
lines changed

levelup/dashboard/integration.py

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import logging
23
import typing as t
34
from pathlib import Path
45

@@ -10,6 +11,7 @@
1011
from ..common import formatter
1112

1213
_ = Translator("LevelUp", __file__)
14+
log = logging.getLogger("red.levelup.dashboard")
1315

1416

1517
def dashboard_page(*args, **kwargs):
@@ -23,7 +25,9 @@ def decorator(func: t.Callable):
2325
class DashboardIntegration(MixinMeta):
2426
@commands.Cog.listener()
2527
async def on_dashboard_cog_add(self, dashboard_cog: commands.Cog) -> None:
28+
log.info("Dashboard cog added, registering third party.")
2629
dashboard_cog.rpc.third_parties_handler.add_third_party(self)
30+
logging.getLogger("werkzeug").setLevel(logging.WARNING)
2731

2832
async def get_dashboard_leaderboard(
2933
self,
@@ -34,6 +38,30 @@ async def get_dashboard_leaderboard(
3438
query: t.Optional[str] = None,
3539
**kwargs,
3640
):
41+
"""
42+
kwargs = {
43+
"user_id": int,
44+
"guild_id": int,
45+
"method": str, # GET or POST
46+
"request_url": str,
47+
"cxrf_token": str,
48+
"wtf_csrf_secret_key": bytes,
49+
"extra_kwargs": MultiDict,
50+
"data": {
51+
"form": ImmutableMultiDict,
52+
"json": ImmutableMultiDict,
53+
}
54+
"lang_code": str,
55+
"Form": FlaskForm,
56+
"DpyObjectConverter": DpyObjectConverter,
57+
"get_sorted_channels": Callable,
58+
"get_sorted_roles": Callable,
59+
"Pagination": Pagination,
60+
}
61+
"""
62+
log.warning("Kwargs")
63+
for k, v in kwargs.items():
64+
log.warning(f"{k}: {v}")
3765
conf = self.db.get_conf(guild)
3866
if lbtype == "weekly":
3967
if not conf.weeklysettings.on:
@@ -49,8 +77,32 @@ async def get_dashboard_leaderboard(
4977
"error_message": _("There is no data for the weekly leaderboard yet, please chat a bit first."),
5078
}
5179

52-
source_path = Path(__file__).parent / "templates" / "leaderboard.html"
53-
payload: dict = await asyncio.to_thread(
80+
parent = Path(__file__).parent
81+
82+
source_path = parent / "templates" / "leaderboard.html"
83+
static_dir = parent / "static"
84+
js_path = static_dir / "js" / "leaderboard.js"
85+
css_path = static_dir / "css" / "leaderboard.css"
86+
87+
# Inject JS and CSS into the HTML source for full page loads
88+
source = (
89+
f"<style>\n{css_path.read_text()}\n</style>\n\n"
90+
+ source_path.read_text().strip()
91+
+ f"\n\n<script>\n{js_path.read_text()}\n</script>"
92+
)
93+
94+
"""
95+
{
96+
"title": str,
97+
"description": str,
98+
"stat": str,
99+
"stats": [{"position": int, "name": str, "id": int, "stat": str}]
100+
"total": str,
101+
"type": leaderboard type, // lb or weekly
102+
"user_position": int, // Index of the user in the leaderboard
103+
}
104+
"""
105+
res: dict = await asyncio.to_thread(
54106
formatter.get_leaderboard,
55107
bot=self.bot,
56108
guild=guild,
@@ -63,40 +115,40 @@ async def get_dashboard_leaderboard(
63115
dashboard=True,
64116
query=query,
65117
)
66-
# if not payload["stats"]:
67-
# return {
68-
# "status": 1,
69-
# "error_title": _("No stats available"),
70-
# "error_message": _("There is no data for the leaderboard yet, please chat a bit first."),
71-
# }
72-
73-
return {
118+
data = {
119+
"user_id": user.id,
120+
"users": res["stats"],
121+
"stat": stat,
122+
"total": res["description"].replace("`", ""),
123+
"type": lbtype,
124+
"statname": res["stat"],
125+
"query": query if query is not None else "", # Changed to prevent None
126+
"page": int(kwargs["extra_kwargs"].get("page", 1)), # Ensure it's an integer
127+
}
128+
content = {
74129
"status": 0,
75130
"web_content": {
76-
"source": source_path.read_text(),
77-
"users": kwargs["Pagination"].from_list(
78-
payload["stats"],
79-
per_page=kwargs["extra_kwargs"].get("per_page"),
80-
page=kwargs["extra_kwargs"].get("page"),
81-
default_per_page=100,
82-
),
131+
"source": source,
132+
"data": data,
83133
"stat": stat,
84-
"total": payload["description"].replace("`", ""),
85-
"statname": payload["stat"],
134+
"total": res["description"].replace("`", ""),
135+
"statname": res["stat"],
86136
"query": query,
137+
"current_user": user,
87138
},
88139
}
140+
return content
89141

90142
@dashboard_page(name="leaderboard", description="Display the guild leaderboard.")
91143
async def leaderboard_page(
92144
self, user: discord.User, guild: discord.Guild, stat: str = None, query: t.Optional[str] = None, **kwargs
93145
) -> t.Dict[str, t.Any]:
94146
stat = stat if stat is not None and stat in {"exp", "messages", "voice", "stars"} else "exp"
95-
return await self.get_dashboard_leaderboard(user, guild, "normal", stat, query, **kwargs)
147+
return await self.get_dashboard_leaderboard(user, guild, "lb", stat, query, **kwargs)
96148

97149
@dashboard_page(name="weekly", description="Display the guild weekly leaderboard.")
98150
async def weekly_page(
99-
self, user: discord.User, guild: discord.Guild, stat: str = None, **kwargs
151+
self, user: discord.User, guild: discord.Guild, stat: str = None, query: t.Optional[str] = None, **kwargs
100152
) -> t.Dict[str, t.Any]:
101153
stat = stat if stat is not None and stat in {"exp", "messages", "voice", "stars"} else "exp"
102-
return await self.get_dashboard_leaderboard(user, guild, "weekly", stat, **kwargs)
154+
return await self.get_dashboard_leaderboard(user, guild, "weekly", stat, query, **kwargs)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/* Trippy search bar styling */
2+
@keyframes gradient-shift {
3+
0% { background-position: 0% 50%; }
4+
50% { background-position: 100% 50%; }
5+
100% { background-position: 0% 50%; }
6+
}
7+
8+
@keyframes glow-pulse {
9+
0% { box-shadow: 0 0 5px rgba(255, 0, 255, 0.7); }
10+
50% { box-shadow: 0 0 20px rgba(0, 255, 255, 0.9); }
11+
100% { box-shadow: 0 0 5px rgba(255, 0, 255, 0.7); }
12+
}
13+
14+
@keyframes wobble {
15+
0%, 100% { transform: translateX(0) rotate(0); }
16+
25% { transform: translateX(-5px) rotate(-1deg); }
17+
75% { transform: translateX(5px) rotate(1deg); }
18+
}
19+
20+
/* Default search input styles */
21+
.search-container input[type="text"] {
22+
transition: all 0.3s ease;
23+
}
24+
25+
/* Trippy styles applied conditionally */
26+
.search-container input[type="text"].trippy-search {
27+
background: linear-gradient(45deg, #ff00e1, #00ffff, #ff00a2, #00ff9d, #8400ff, #00e1ff);
28+
background-size: 600% 600%;
29+
animation: gradient-shift 10s ease infinite, glow-pulse 3s infinite;
30+
color: white;
31+
text-shadow: 1px 1px 2px black;
32+
font-weight: bold;
33+
border: 2px solid transparent;
34+
/* border-image: linear-gradient(to right, violet, indigo, blue, green, yellow, orange, red); */
35+
border-image-slice: 1;
36+
}
37+
38+
.search-container input[type="text"].trippy-search:focus {
39+
animation: gradient-shift 3s ease infinite, glow-pulse 1.5s infinite, wobble 2s ease-in-out infinite;
40+
transform: scale(1.02);
41+
outline: none;
42+
}
43+
44+
.search-container input[type="text"].trippy-search::placeholder {
45+
color: rgba(255, 255, 255, 0.8);
46+
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
47+
}
48+
49+
.search-container input[type="text"].trippy-search:not(:placeholder-shown) {
50+
font-weight: bold;
51+
letter-spacing: 0.5px;
52+
}
53+
54+
/* Toggle switch styling */
55+
.form-switch .form-check-input {
56+
cursor: pointer;
57+
}
58+
59+
.form-check-label {
60+
font-size: 0.8rem;
61+
cursor: pointer;
62+
}
63+
64+
/* Sleek Toggle Switch */
65+
.trippy-toggle {
66+
position: relative;
67+
display: inline-block;
68+
}
69+
70+
.trippy-toggle .toggle-input {
71+
opacity: 0;
72+
width: 0;
73+
height: 0;
74+
}
75+
76+
.trippy-toggle .toggle-label {
77+
position: relative;
78+
display: inline-block;
79+
width: 30px;
80+
height: 16px;
81+
background-color: #ccc;
82+
border-radius: 8px;
83+
cursor: pointer;
84+
transition: background-color 0.3s ease;
85+
}
86+
87+
.trippy-toggle .toggle-label:before {
88+
position: absolute;
89+
content: '';
90+
height: 12px;
91+
width: 12px;
92+
left: 2px;
93+
bottom: 2px;
94+
background-color: white;
95+
border-radius: 50%;
96+
transition: 0.3s;
97+
}
98+
99+
.trippy-toggle .toggle-input:checked + .toggle-label {
100+
background: linear-gradient(90deg, #ff00e1, #00ffff);
101+
box-shadow: 0 0 5px rgba(0, 255, 255, 0.5);
102+
}
103+
104+
.trippy-toggle .toggle-input:checked + .toggle-label:before {
105+
transform: translateX(14px);
106+
}
107+
108+
.search-input-container {
109+
position: relative;
110+
}

0 commit comments

Comments
 (0)