Skip to content

Commit 541fbfe

Browse files
committed
CHANGES:
- GreenieBoard: support light and dark mode and reverse display
1 parent ae25127 commit 541fbfe

File tree

4 files changed

+73
-40
lines changed

4 files changed

+73
-40
lines changed

plugins/greenieboard/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ different greenieboards for different servers. If that is a user demand in the f
8686
DEFAULT:
8787
num_landings: 5 # display the last 5 landings
8888
num_rows: 10 # display 10 players
89+
theme: light # one of 'dark' or 'light'
90+
landings_rtl: false # draw landings right to left (default: True)
8991
persistent_board: false # if true, a persistent board will be uploaded into persistent_channel
9092
persistent_channel: 123456789 # in which channel to post the board
9193
squadrons:

plugins/greenieboard/commands.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from matplotlib import pyplot as plt
1717
from psycopg.rows import dict_row
1818
from services.bot import DCSServerBot
19-
from typing import Optional, Union
19+
from typing import Optional, Union, Literal
2020

2121
from . import GRADES
2222
from .listener import GreenieBoardEventListener
@@ -224,13 +224,17 @@ def format_landing(landing: dict) -> str:
224224
@app_commands.rename(num_landings='landings')
225225
@app_commands.autocomplete(squadron_id=utils.squadron_autocomplete)
226226
@app_commands.rename(squadron_id="squadron")
227+
@app_commands.describe(rtl=_("Draw landings right to left (default: True)"))
227228
async def board(self, interaction: discord.Interaction,
228229
num_rows: Optional[Range[int, 5, 20]] = 10,
229230
num_landings: Optional[Range[int, 1, 30]] = 30,
231+
theme: Optional[Literal['light', 'dark']] = 'dark',
232+
landings_rtl: Optional[bool] = True,
230233
squadron_id: Optional[int] = None):
231234
report = PaginationReport(interaction, self.plugin_name, 'greenieboard.json')
232235
squadron = utils.get_squadron(self.node, squadron_id=squadron_id) if squadron_id else None
233-
await report.render(server_name=None, num_rows=num_rows, num_landings=num_landings, squadron=squadron)
236+
await report.render(server_name=None, num_rows=num_rows, num_landings=num_landings, theme=theme,
237+
landings_rtl=landings_rtl, squadron=squadron)
234238

235239
@traps.command(description=_('Adds a trap to the Greenieboard'))
236240
@app_commands.guild_only()

plugins/greenieboard/listener.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,12 @@ async def update_greenieboard(self, server: Server):
5858
channel_id = int(config.get('persistent_channel', server.channels[Channel.STATUS]))
5959
num_rows = config.get('num_rows', 10)
6060
num_landings = config.get('num_landings', 30)
61+
theme = config.get('theme', 'dark')
62+
landings_rtl = config.get('landings_rtl', True)
6163
report = PersistentReport(self.bot, self.plugin_name, 'greenieboard.json',
6264
embed_name='greenieboard', server=server, channel_id=channel_id)
63-
await report.render(server_name=server.name, num_rows=num_rows, num_landings=num_landings)
65+
await report.render(server_name=server.name, num_rows=num_rows, num_landings=num_landings, theme=theme,
66+
landings_rtl=landings_rtl)
6467
squadrons = config.get('squadrons', [])
6568
if squadrons:
6669
for squadron in squadrons:
@@ -72,16 +75,19 @@ async def update_greenieboard(self, server: Server):
7275
embed_name=f"greenieboard_s{row['id']}", server=server,
7376
channel_id=squadron.get('channel', channel_id))
7477
await report.render(server_name=server.name, num_rows=num_rows, num_landings=num_landings,
75-
squadron=row)
78+
theme=theme, landings_rtl=landings_rtl, squadron=row)
7679
# update the global board
7780
config = self.get_config()
7881
if 'persistent_channel' in config and config.get('persistent_board', False):
7982
channel_id = int(config.get('persistent_channel'))
8083
num_rows = config.get('num_rows', 10)
8184
num_landings = config.get('num_landings', 30)
85+
theme = config.get('theme', 'dark')
86+
landings_rtl = config.get('landings_rtl', True)
8287
report = PersistentReport(self.bot, self.plugin_name, 'greenieboard.json',
8388
embed_name='greenieboard', channel_id=channel_id)
84-
await report.render(server_name=None, num_rows=num_rows, num_landings=num_landings)
89+
await report.render(server_name=None, num_rows=num_rows, num_landings=num_landings, theme=theme,
90+
landings_rtl=landings_rtl)
8591
squadrons = config.get('squadrons', [])
8692
if squadrons:
8793
for squadron in squadrons:
@@ -93,7 +99,7 @@ async def update_greenieboard(self, server: Server):
9399
embed_name=f"greenieboard_s{row['id']}",
94100
channel_id=squadron.get('channel', channel_id))
95101
await report.render(server_name=None, num_rows=num_rows, num_landings=num_landings,
96-
squadron=row)
102+
theme=theme, landings_rtl=landings_rtl, squadron=row)
97103
except FileNotFoundError as ex:
98104
self.log.error(f' => File not found: {ex}')
99105
except Exception as ex:

plugins/greenieboard/reports.py

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def deflate_comment(_element: str) -> list[str]:
119119
class TrapSheet(report.EmbedElement):
120120
async def render(self, landing: dict):
121121
async with self.apool.connection() as conn:
122-
cursor = await conn.execute("SELECT trapsheet FROM traps WHERE id = %s", (landing['id'], ))
122+
cursor = await conn.execute("SELECT trapsheet FROM traps WHERE id = %s", (landing['id'],))
123123
trapsheet = (await cursor.fetchone())[0]
124124
if trapsheet:
125125
self.env.filename = 'trapsheet.png'
@@ -131,10 +131,12 @@ class HighscoreTraps(report.GraphElement):
131131
async def render(self, interaction: discord.Interaction, server_name: str, limit: int,
132132
flt: StatisticsFilter, include_bolters: bool = False, include_waveoffs: bool = False,
133133
bar_labels: Optional[bool] = True):
134-
sql = "SELECT p.discord_id, COALESCE(p.name, 'Unknown') AS name, COUNT(g.*) AS value " \
135-
"FROM traps g, missions m, statistics s, players p " \
136-
"WHERE g.mission_id = m.id AND s.mission_id = m.id AND g.player_ucid = s.player_ucid " \
137-
"AND g.player_ucid = p.ucid AND g.unit_type = s.slot AND g.time BETWEEN s.hop_on AND s.hop_off "
134+
sql = """
135+
SELECT p.discord_id, COALESCE(p.name, 'Unknown') AS name, COUNT(g.*) AS value
136+
FROM traps g, missions m, statistics s, players p
137+
WHERE g.mission_id = m.id AND s.mission_id = m.id AND g.player_ucid = s.player_ucid
138+
AND g.player_ucid = p.ucid AND g.unit_type = s.slot AND g.time BETWEEN s.hop_on AND s.hop_off
139+
"""
138140
if server_name:
139141
sql += "AND m.server_name = %(server_name)s"
140142
self.env.embed.description = utils.escape_string(server_name)
@@ -188,7 +190,7 @@ def __init__(self, env: ReportEnv, rows: int, cols: int, row: Optional[int] = 0,
188190
super().__init__(env, rows, cols, row, col, colspan, rowspan)
189191
self.plugin: Plugin = cast(Plugin, env.bot.cogs.get('GreenieBoard'))
190192

191-
def add_legend(self, config: dict, start_y, card_size=0.4, font_size=14, num_landings=30):
193+
def add_legend(self, config: dict, start_y, card_size=0.4, font_size=14, num_landings=30, text_color='white'):
192194
grades = GRADES | config.get('grades', {})
193195
num_columns = 5 if num_landings < 20 else 3
194196
padding = 0.2
@@ -209,7 +211,7 @@ def add_legend(self, config: dict, start_y, card_size=0.4, font_size=14, num_lan
209211
x_pos = x_start + col * (card_size + padding) + offset
210212
y_pos = y_position - row * (card_size + padding)
211213

212-
# Draw unicorn image if needed
214+
# Draw the unicorn image if needed
213215
if key == '_n':
214216
self.axes.plot(x_pos + card_size / 2, y_pos, 'o', color='black', markersize=10, zorder=3)
215217
elif key == '_OK_':
@@ -238,9 +240,10 @@ def add_legend(self, config: dict, start_y, card_size=0.4, font_size=14, num_lan
238240

239241
# Add text for legend dynamically adjusted
240242
self.axes.text(x_pos + card_size + padding, y_pos, text, va='center', ha='left', fontsize=font_size,
241-
color='white')
243+
color=text_color)
242244

243-
async def render(self, server_name: str, num_rows: int, num_landings: int, squadron: Optional[dict] = None):
245+
async def render(self, server_name: str, num_rows: int, num_landings: int, squadron: Optional[dict] = None,
246+
theme: str = 'dark', landings_rtl=True):
244247

245248
title = self.env.embed.title
246249
self.env.embed.title = ""
@@ -252,16 +255,18 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
252255
font_name = None
253256

254257
sql1 = """
255-
SELECT g.player_ucid, p.name, g.points, MAX(g.time) AS time FROM (
256-
SELECT player_ucid, ROW_NUMBER() OVER w AS rn,
257-
AVG(points) OVER w AS points,
258-
MAX(time) OVER w AS time
259-
FROM traps
260-
"""
258+
SELECT g.player_ucid, p.name, g.points, MAX(g.time) AS time
259+
FROM (SELECT player_ucid,
260+
ROW_NUMBER() OVER w AS rn,
261+
AVG(points) OVER w AS points,
262+
MAX(time) OVER w AS time
263+
FROM traps
264+
"""
261265
sql2 = """
262-
SELECT TRIM(grade) as "grade", night FROM traps
263-
WHERE player_ucid = %(player_ucid)s
264-
"""
266+
SELECT TRIM(grade) as "grade", night
267+
FROM traps
268+
WHERE player_ucid = %(player_ucid)s
269+
"""
265270
if server_name:
266271
title = utils.escape_string(server_name)
267272
sql1 += """
@@ -303,7 +308,18 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
303308
server = self.bot.servers.get(server_name)
304309
config = self.plugin.get_config(server)
305310
grades = GRADES | config.get('grades', {})
306-
plt.title(f'{title}', color='white', fontsize=30, fontname=font_name)
311+
312+
if theme == 'light':
313+
text_color = 'black'
314+
bg_color = 'white'
315+
odd_row_bg_color = '#F0F0F0' # For odd rows
316+
else:
317+
text_color = 'white'
318+
bg_color = '#2A2A2A'
319+
odd_row_bg_color = '#3A3A3A' # For odd rows
320+
321+
plt.title(f'{title}', color=text_color, fontsize=30, fontname=font_name)
322+
plt.gca().set_facecolor(bg_color)
307323

308324
async with self.apool.connection() as conn:
309325
async with conn.cursor(row_factory=dict_row) as cursor:
@@ -329,7 +345,8 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
329345
# Calculate dynamic figure size based on rows and columns
330346
pilot_column_width = max([len(item['name']) for item in rows]) * 0.20
331347
padding = 1.0 # Padding between columns
332-
fig_width = pilot_column_width + padding + (num_columns * column_width) + 2 # Additional padding on the sides
348+
fig_width = pilot_column_width + padding + (
349+
num_columns * column_width) + 2 # Additional padding on the sides
333350
legend_height = (5 if num_landings < 20 else 3) * (card_size + 0.2)
334351
fig_height = (num_rows * row_height) + 2 + legend_height # Additional padding on the top and bottom
335352

@@ -338,20 +355,18 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
338355

339356
self.env.figure.set_size_inches(fig_width, fig_height)
340357

341-
bg_color = '#2A2A2A'
342-
gray_color = '#3A3A3A' # For odd rows
343358
rounding_radius = 0.1 # Radius for rounded corners
344359

345360
# Plot table headers with proper padding
346-
self.axes.text(0, 0, "Pilot", va='center', ha='left', fontsize=text_size, color='white',
361+
self.axes.text(0, 0, "Pilot", va='center', ha='left', fontsize=text_size, color=text_color,
347362
fontweight='bold', fontname=font_name)
348363
self.axes.text(pilot_column_width + padding, 0, "AVG", va='center', ha='center', fontsize=text_size,
349-
color='white', fontweight='bold', fontname=font_name)
364+
color=text_color, fontweight='bold', fontname=font_name)
350365

351366
# Add dynamic column headers directly above the card columns
352367
for j in range(num_columns):
353368
x_pos = pilot_column_width + padding + 1 + j * (card_size + 0.2) + card_size / 2
354-
self.axes.text(x_pos, 0, str(j + 1), va='center', ha='center', fontsize=text_size, color='white',
369+
self.axes.text(x_pos, 0, str(j + 1), va='center', ha='center', fontsize=text_size, color=text_color,
355370
fontweight='bold', fontname=font_name)
356371

357372
for i, row in enumerate(rows):
@@ -360,18 +375,19 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
360375
# Add a light gray background to odd rows
361376
if i % 2 == 0:
362377
self.axes.add_patch(plt.Rectangle((-0.5, y_position - row_height / 2), fig_width, row_height,
363-
color=gray_color, zorder=1))
378+
color=odd_row_bg_color, zorder=1))
364379

365380
member = self.bot.get_member_by_ucid(row['player_ucid'])
366381
if member:
367382
name = member.display_name
368383
else:
369384
name = row['name']
370385

371-
self.axes.text(0, y_position, name, va='center', ha='left', fontsize=text_size, color='white',
372-
fontweight='bold', fontname=font_name)
373-
self.axes.text(pilot_column_width + padding, y_position, f'{row["points"]:.1f}', va='center', ha='center',
374-
fontsize=text_size, color='white', fontname=font_name)
386+
self.axes.text(0, y_position, name, va='center', ha='left', fontsize=text_size, color=text_color,
387+
fontweight='bold', fontname=font_name)
388+
self.axes.text(pilot_column_width + padding, y_position, f'{row["points"]:.1f}', va='center',
389+
ha='center',
390+
fontsize=text_size, color=text_color, fontname=font_name)
375391

376392
await cursor.execute(sql2, {
377393
"player_ucid": row['player_ucid'],
@@ -380,6 +396,9 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
380396
})
381397

382398
landings = await cursor.fetchall()
399+
if not landings_rtl:
400+
landings.reverse()
401+
383402
for j in range(num_columns):
384403
x_pos = pilot_column_width + padding + 1 + j * (card_size + 0.2)
385404
if j < len(landings):
@@ -393,7 +412,8 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
393412

394413
if grade == '_OK_':
395414
imagebox = OffsetImage(unicorn_image, zoom=1, resample=True)
396-
ab = AnnotationBbox(imagebox, (x_pos + card_size / 2, y_position), frameon=False, zorder=3)
415+
ab = AnnotationBbox(imagebox, (x_pos + card_size / 2, y_position), frameon=False,
416+
zorder=3)
397417
self.axes.add_artist(ab)
398418
else:
399419
rect = FancyBboxPatch((x_pos, y_position - card_size / 2),
@@ -404,7 +424,8 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
404424
self.axes.add_patch(rect)
405425
# mark night passes
406426
if landings[j]['night']:
407-
self.axes.plot(x_pos + card_size / 2, y_position, 'o', color='black', markersize=10, zorder=3)
427+
self.axes.plot(x_pos + card_size / 2, y_position, 'o', color='black', markersize=10,
428+
zorder=3)
408429

409430
else:
410431
# Draw true rounded brackets using arcs and lines
@@ -429,8 +450,8 @@ async def render(self, server_name: str, num_rows: int, num_landings: int, squad
429450
color='grey', lw=1.5)
430451

431452
legend_start_y = -row_height * num_rows - 1.5
432-
self.add_legend(config=config, start_y=legend_start_y, num_landings=num_landings)
453+
self.add_legend(config=config, start_y=legend_start_y, num_landings=num_landings, text_color=text_color)
433454
self.axes.set_xlim(-0.5, fig_width - 0.5)
434455
self.axes.set_ylim(-fig_height + 1, 0.5)
435456
self.axes.axis('off')
436-
self.env.figure.set_facecolor(bg_color)
457+
self.env.figure.patch.set_facecolor(bg_color)

0 commit comments

Comments
 (0)