Skip to content

Commit b2ad2c6

Browse files
committed
Dynamically update options in select menu based on current page.
1 parent 29c7cf0 commit b2ad2c6

File tree

1 file changed

+82
-40
lines changed

1 file changed

+82
-40
lines changed

core/paginator.py

+82-40
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def __init__(self, ctx: commands.Context, *pages, **options):
5858
">": self.next_page,
5959
">>": self.last_page,
6060
}
61-
self._buttons_map = {"<<": None, "<": None, ">": None, ">>": None}
61+
self._buttons_map = {k: None for k in self.callback_map.keys()}
6262

6363
async def show_page(self, index: int) -> typing.Optional[typing.Dict]:
6464
"""
@@ -84,34 +84,19 @@ async def show_page(self, index: int) -> typing.Optional[typing.Dict]:
8484
self.update_disabled_status()
8585
return result
8686

87-
def update_disabled_status(self):
88-
if self.current == self.first_page():
89-
# disable << button
90-
if self._buttons_map["<<"] is not None:
91-
self._buttons_map["<<"].disabled = True
92-
93-
if self._buttons_map["<"] is not None:
94-
self._buttons_map["<"].disabled = True
95-
else:
96-
if self._buttons_map["<<"] is not None:
97-
self._buttons_map["<<"].disabled = False
98-
99-
if self._buttons_map["<"] is not None:
100-
self._buttons_map["<"].disabled = False
101-
102-
if self.current == self.last_page():
103-
# disable >> button
104-
if self._buttons_map[">>"] is not None:
105-
self._buttons_map[">>"].disabled = True
106-
107-
if self._buttons_map[">"] is not None:
108-
self._buttons_map[">"].disabled = True
109-
else:
110-
if self._buttons_map[">>"] is not None:
111-
self._buttons_map[">>"].disabled = False
112-
113-
if self._buttons_map[">"] is not None:
114-
self._buttons_map[">"].disabled = False
87+
def update_disabled_status(self) -> None:
88+
for label, button in self._buttons_map.items():
89+
if button is None:
90+
continue
91+
elif any(
92+
(
93+
self.current == self.first_page() and label in ("<<", "<"),
94+
self.current == self.last_page() and label in (">>", ">"),
95+
)
96+
):
97+
button.disabled = True
98+
else:
99+
button.disabled = False
115100

116101
async def create_base(self, item) -> None:
117102
"""
@@ -227,7 +212,7 @@ def __init__(self, handler: PaginatorSession, *args, **kwargs):
227212
async def stop_button(self, interaction: Interaction, button: Button):
228213
await self.handler.close(interaction=interaction)
229214

230-
def fill_items(self):
215+
def fill_items(self) -> None:
231216
if self.handler.select_menu is not None:
232217
self.add_item(self.handler.select_menu)
233218

@@ -246,7 +231,7 @@ def fill_items(self):
246231
self.add_item(button)
247232
self.add_item(self.stop_button)
248233

249-
async def interaction_check(self, interaction: Interaction):
234+
async def interaction_check(self, interaction: Interaction) -> bool:
250235
"""Only allow the message author to interact"""
251236
if interaction.user != self.handler.ctx.author:
252237
await interaction.response.send_message(
@@ -280,34 +265,88 @@ def __init__(self, handler, page_callback, **kwargs):
280265
self.handler = handler
281266
self.page_callback = page_callback
282267

283-
async def callback(self, interaction: Interaction):
268+
async def callback(self, interaction: Interaction) -> None:
284269
kwargs = await self.handler.show_page(self.page_callback())
270+
select_menu = self.handler.select_menu
271+
if select_menu is not None:
272+
select_menu.update_options(True)
285273
await interaction.response.edit_message(**kwargs, view=self.view)
286274

287275

288276
class PageSelect(Select):
289277
def __init__(self, handler: PaginatorSession, pages: typing.List[typing.Tuple[str]]):
290278
self.handler = handler
291-
options = []
279+
self._all_options = [] # no limits
292280
for n, (label, description) in enumerate(pages):
293-
options.append(discord.SelectOption(label=label, description=description, value=str(n)))
281+
self._all_options.append(discord.SelectOption(label=label, description=description, value=str(n)))
294282

295-
options = options[:25] # max 25 options
283+
options = self.update_options()
296284
super().__init__(placeholder="Select a page", min_values=1, max_values=1, options=options)
297285

298-
async def callback(self, interaction: Interaction):
286+
def update_options(self, refresh_options: bool = False) -> typing.List[discord.SelectOption]:
287+
"""
288+
A helper to dynamically update the select menu options based on the current page.
289+
"""
290+
current = self.handler.current
291+
n = prev = after = 0
292+
293+
def max_reached():
294+
return prev + after >= 25 # max select options
295+
296+
while not max_reached():
297+
differ = n + 1
298+
inc_prev = current - differ >= 0
299+
inc_next = current + differ <= len(self._all_options)
300+
if not any((inc_prev, inc_next)):
301+
break
302+
if inc_prev and not max_reached():
303+
prev += 1
304+
if inc_next and not max_reached():
305+
after += 1
306+
n += 1
307+
308+
options = self._all_options[current - prev : current + after]
309+
if refresh_options:
310+
self.options.clear()
311+
curr_option = self._all_options[current]
312+
for option in options:
313+
option.default = option == curr_option
314+
self.append_option(option)
315+
return options
316+
317+
async def callback(self, interaction: Interaction) -> None:
299318
page = int(self.values[0])
300319
kwargs = await self.handler.show_page(page)
320+
self.update_options(True)
301321
await interaction.response.edit_message(**kwargs, view=self.view)
302322

303323

304324
class EmbedPaginatorSession(PaginatorSession):
305-
def __init__(self, ctx: commands.Context, *embeds, **options):
325+
"""
326+
Class that interactively paginates embed pages.
327+
This inherits from PaginatorSession.
328+
329+
Parameters
330+
----------
331+
ctx : Context
332+
The context of the command.
333+
embeds : List[discord.Embed]
334+
A list of entries to paginate.
335+
create_select : bool
336+
Whether to create the select menu. Defaults to True.
337+
"""
338+
339+
def __init__(
340+
self,
341+
ctx: commands.Context,
342+
*embeds: typing.List[discord.Embed],
343+
create_select: bool = True,
344+
**options,
345+
):
306346
super().__init__(ctx, *embeds, **options)
307347

308348
if len(self.pages) > 1:
309349
select_options = []
310-
create_select = True
311350
for i, embed in enumerate(self.pages):
312351
footer_text = f"Page {i + 1} of {len(self.pages)}"
313352
if embed.footer.text:
@@ -320,6 +359,9 @@ def __init__(self, ctx: commands.Context, *embeds, **options):
320359
embed.set_footer(text=footer_text, icon_url=icon_url)
321360

322361
# select menu
362+
if not create_select:
363+
continue
364+
323365
if embed.author.name:
324366
title = embed.author.name[:30].strip()
325367
if len(embed.author.name) > 30:
@@ -355,7 +397,7 @@ def add_page(self, item: Embed) -> None:
355397
async def _create_base(self, item: Embed, view: View) -> None:
356398
self.base = await self.destination.send(embed=item, view=view)
357399

358-
def _show_page(self, page):
400+
def _show_page(self, page) -> typing.Dict:
359401
return dict(embed=page)
360402

361403

@@ -371,7 +413,7 @@ def add_page(self, item: str) -> None:
371413
else:
372414
raise TypeError("Page must be a str object.")
373415

374-
def _set_footer(self):
416+
def _set_footer(self) -> None:
375417
if self.embed is not None:
376418
footer_text = f"Page {self.current+1} of {len(self.pages)}"
377419
if self.footer_text:

0 commit comments

Comments
 (0)