24
24
from jishaku .hljs import get_language , guess_file_traits
25
25
from jishaku .types import BotT , ContextA
26
26
27
+ if typing .TYPE_CHECKING :
28
+ from discord .types .components import ButtonComponent
29
+
27
30
__all__ = ('EmojiSettings' , 'PaginatorInterface' , 'PaginatorEmbedInterface' ,
28
31
'WrappedPaginator' , 'FilePaginator' , 'use_file_check' )
29
32
@@ -214,22 +217,37 @@ class EmojiSettings(typing.NamedTuple):
214
217
)
215
218
216
219
217
- T = typing .TypeVar ('T' , bound = ui .View )
218
-
219
- MaybeButton = typing .Union [discord .Interaction , ui .Button [T ]]
220
+ V_co = typing .TypeVar ('V_co' , bound = 'ui.View' , covariant = True )
220
221
221
222
222
- def button_either_arg (
223
- a : MaybeButton [T ],
224
- b : MaybeButton [T ]
225
- ) -> typing .Tuple [discord .Interaction , ui .Button [T ]]:
223
+ class DynamicButton (ui .Button [V_co ]):
226
224
"""
227
- Compatibility function to allow interaction and button to come in either order
225
+ A ui.Button that has its label be a callback instead of a string.
228
226
"""
229
227
230
- if isinstance (a , discord .Interaction ):
231
- return (a , b ) # type: ignore
232
- return (b , a ) # type: ignore
228
+ def __init__ (
229
+ self ,
230
+ callback : typing .Callable [[discord .Interaction ], typing .Coroutine [typing .Any , typing .Any , typing .Any ]],
231
+ label_callback : typing .Callable [[typing .Self ], str ],
232
+ ** kwargs , # type: ignore
233
+ ):
234
+ super ().__init__ (** kwargs ) # type: ignore
235
+
236
+ self .callback = callback # type: ignore
237
+ self .label_callback = label_callback
238
+
239
+ @property
240
+ def label (self ) -> str :
241
+ return self .label_callback (self )
242
+
243
+ @label .setter
244
+ def label (self , value : typing .Any ):
245
+ # Absorbed to be compatible with ui.Button
246
+ pass
247
+
248
+ def to_component_dict (self ) -> 'ButtonComponent' :
249
+ self ._underlying .label = str (self .label_callback (self ))
250
+ return self ._underlying .to_dict ()
233
251
234
252
235
253
class PaginatorInterface (ui .View ): # pylint: disable=too-many-instance-attributes
@@ -269,7 +287,7 @@ class PaginatorInterface(ui.View): # pylint: disable=too-many-instance-attribut
269
287
await interface.add_line("I'm still here!")
270
288
"""
271
289
272
- def __init__ (self , bot : BotT , paginator : commands .Paginator , ** kwargs : typing .Any ):
290
+ def __init__ (self , bot : BotT , paginator : commands .Paginator , additional_buttons : typing . Optional [ typing . List [ ui . Button [ typing . Self ]]] = None , ** kwargs : typing .Any ):
273
291
if not isinstance (paginator , commands .Paginator ): # type: ignore
274
292
raise TypeError ('paginator must be a commands.Paginator instance' )
275
293
@@ -300,6 +318,39 @@ def __init__(self, bot: BotT, paginator: commands.Paginator, **kwargs: typing.An
300
318
301
319
super ().__init__ (timeout = self .timeout_length )
302
320
321
+ self .button_start : DynamicButton [typing .Self ] = DynamicButton (self .button_start_callback , self .button_start_label , style = discord .ButtonStyle .secondary )
322
+ self .button_previous : DynamicButton [typing .Self ] = DynamicButton (self .button_previous_callback , self .button_previous_label , style = discord .ButtonStyle .secondary )
323
+ self .button_current : DynamicButton [typing .Self ] = DynamicButton (self .button_current_callback , self .button_current_label , style = discord .ButtonStyle .primary )
324
+ self .button_next : DynamicButton [typing .Self ] = DynamicButton (self .button_next_callback , self .button_next_label , style = discord .ButtonStyle .secondary )
325
+ self .button_last : DynamicButton [typing .Self ] = DynamicButton (self .button_last_callback , self .button_last_label , style = discord .ButtonStyle .secondary )
326
+ self .button_goto : DynamicButton [typing .Self ] = DynamicButton (self .button_goto_callback , self .button_goto_label , style = discord .ButtonStyle .primary )
327
+ self .button_close : DynamicButton [typing .Self ] = DynamicButton (self .button_close_callback , self .button_close_label , style = discord .ButtonStyle .danger )
328
+
329
+ self .additional_buttons = additional_buttons or []
330
+
331
+ self .buttons : typing .List [ui .Button [typing .Self ]] = self .button_definitions ()
332
+
333
+ for button in self .buttons :
334
+ self .add_item (button )
335
+
336
+ def button_definitions (self ) -> typing .List [ui .Button [typing .Self ]]:
337
+ """
338
+ This is an overridable function you can use to remove buttons or customize their order.
339
+
340
+ If you only need to add buttons, consider passing `additional_buttons` to the constructor.
341
+ """
342
+
343
+ return [
344
+ self .button_start ,
345
+ self .button_previous ,
346
+ self .button_current ,
347
+ self .button_next ,
348
+ self .button_last ,
349
+ * self .additional_buttons ,
350
+ self .button_goto ,
351
+ self .button_close ,
352
+ ]
353
+
303
354
@property
304
355
def pages (self ):
305
356
"""
@@ -368,19 +419,6 @@ def send_kwargs(self) -> typing.Dict[str, typing.Any]:
368
419
content = self .pages [self .display_page ]
369
420
return {'content' : content , 'view' : self }
370
421
371
- def update_view (self ):
372
- """
373
- Updates view buttons to correspond to current interface state.
374
- This is used internally.
375
- """
376
-
377
- self .button_start .label = f"1 \u200b { self .emojis .start } "
378
- self .button_previous .label = str (self .emojis .back )
379
- self .button_current .label = str (self .display_page + 1 )
380
- self .button_next .label = str (self .emojis .forward )
381
- self .button_last .label = f"{ self .emojis .end } \u200b { self .page_count } "
382
- self .button_close .label = f"{ self .emojis .close } \u200b Close paginator"
383
-
384
422
async def add_line (self , * args : typing .Any , ** kwargs : typing .Any ):
385
423
"""
386
424
A proxy function that allows this PaginatorInterface to remain locked to the last page
@@ -457,8 +495,6 @@ async def wait_loop(self):
457
495
while not self .bot .is_closed ():
458
496
await asyncio .wait_for (self .send_lock_delayed (), timeout = self .timeout_length )
459
497
460
- self .update_view ()
461
-
462
498
try :
463
499
await self .message .edit (** self .send_kwargs )
464
500
except discord .NotFound :
@@ -487,93 +523,85 @@ async def interaction_check(self, *args: typing.Any): # pylint: disable=argumen
487
523
interaction : discord .Interaction
488
524
return not self .owner or interaction .user .id == self .owner .id
489
525
490
- @ui .button (label = "1 \u200b \N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR} " , style = discord .ButtonStyle .secondary )
491
- async def button_start (self , a : MaybeButton ['PaginatorInterface' ], b : MaybeButton ['PaginatorInterface' ]): # pylint: disable=unused-argument
526
+ async def button_start_callback (self , interaction : discord .Interaction ): # pylint: disable=unused-argument
492
527
"""Button to send interface to first page"""
493
528
494
- interaction , _ = button_either_arg (a , b )
495
-
496
529
self ._display_page = 0
497
- self .update_view ()
498
530
await interaction .response .edit_message (** self .send_kwargs )
499
531
500
- @ui .button (label = "\N{BLACK LEFT-POINTING TRIANGLE} " , style = discord .ButtonStyle .secondary )
501
- async def button_previous (self , a : MaybeButton ['PaginatorInterface' ], b : MaybeButton ['PaginatorInterface' ]): # pylint: disable=unused-argument
502
- """Button to send interface to previous page"""
532
+ def button_start_label (self , _button : ui .Button [typing .Self ]) -> str :
533
+ return f"1 \u200b { self .emojis .start } "
503
534
504
- interaction , _ = button_either_arg (a , b )
535
+ async def button_previous_callback (self , interaction : discord .Interaction ): # pylint: disable=unused-argument
536
+ """Button to send interface to previous page"""
505
537
506
538
self ._display_page -= 1
507
- self .update_view ()
508
539
await interaction .response .edit_message (** self .send_kwargs )
509
540
510
- @ui .button (label = "1" , style = discord .ButtonStyle .primary )
511
- async def button_current (self , a : MaybeButton ['PaginatorInterface' ], b : MaybeButton ['PaginatorInterface' ]): # pylint: disable=unused-argument
512
- """Button to refresh the interface"""
541
+ def button_previous_label (self , _button : ui .Button [typing .Self ]) -> str :
542
+ return str (self .emojis .back )
513
543
514
- interaction , _ = button_either_arg (a , b )
544
+ async def button_current_callback (self , interaction : discord .Interaction ): # pylint: disable=unused-argument
545
+ """Button to refresh the interface"""
515
546
516
- self .update_view ()
517
547
await interaction .response .edit_message (** self .send_kwargs )
518
548
519
- @ui .button (label = "\N{BLACK RIGHT-POINTING TRIANGLE} " , style = discord .ButtonStyle .secondary )
520
- async def button_next (self , a : MaybeButton ['PaginatorInterface' ], b : MaybeButton ['PaginatorInterface' ]): # pylint: disable=unused-argument
521
- """Button to send interface to next page"""
549
+ def button_current_label (self , _button : ui .Button [typing .Self ]) -> str :
550
+ return str (self .display_page + 1 )
522
551
523
- interaction , _ = button_either_arg (a , b )
552
+ async def button_next_callback (self , interaction : discord .Interaction ): # pylint: disable=unused-argument
553
+ """Button to send interface to next page"""
524
554
525
555
self ._display_page += 1
526
- self .update_view ()
527
556
await interaction .response .edit_message (** self .send_kwargs )
528
557
529
- @ui .button (label = "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR} \u200b 1" , style = discord .ButtonStyle .secondary )
530
- async def button_last (self , a : MaybeButton ['PaginatorInterface' ], b : MaybeButton ['PaginatorInterface' ]): # pylint: disable=unused-argument
531
- """Button to send interface to last page"""
558
+ def button_next_label (self , _button : ui .Button [typing .Self ]) -> str :
559
+ return str (self .emojis .forward )
532
560
533
- interaction , _ = button_either_arg (a , b )
561
+ async def button_last_callback (self , interaction : discord .Interaction ): # pylint: disable=unused-argument
562
+ """Button to send interface to last page"""
534
563
535
564
self ._display_page = self .page_count - 1
536
- self .update_view ()
537
565
await interaction .response .edit_message (** self .send_kwargs )
538
566
539
- if typing .TYPE_CHECKING or hasattr (ui , 'TextInput' ):
540
- class PageChangeModal (ui .Modal , title = "Go to page" ):
541
- """Modal that prompts users for the page number to change to"""
567
+ def button_last_label (self , _button : ui .Button [typing .Self ]) -> str :
568
+ return f"{ self .emojis .end } \u200b { self .page_count } "
542
569
543
- page_number : ui .TextInput [ui .Modal ] = ui .TextInput (label = "Page number" , style = discord .TextStyle .short )
570
+ class PageChangeModal (ui .Modal , title = "Go to page" ):
571
+ """Modal that prompts users for the page number to change to"""
544
572
545
- def __init__ (self , interface : 'PaginatorInterface' , * args : typing .Any , ** kwargs : typing .Any ):
546
- super ().__init__ (* args , timeout = interface .timeout_length , ** kwargs )
547
- self .interface = interface
548
- self .page_number .label = f"Page number (1-{ interface .page_count } )"
549
- self .page_number .min_length = 1
550
- self .page_number .max_length = len (str (interface .page_count ))
573
+ page_number : ui .TextInput [ui .Modal ] = ui .TextInput (label = "Page number" , style = discord .TextStyle .short )
551
574
552
- async def on_submit (self , interaction : discord .Interaction , / ):
553
- try :
554
- if not self .page_number .value :
555
- raise ValueError ("Page number not filled" )
556
-
557
- self .interface .display_page = int (self .page_number .value ) - 1
558
- except ValueError :
559
- await interaction .response .send_message (
560
- content = f"``{ self .page_number .value } `` could not be converted to a page number" ,
561
- ephemeral = True
562
- )
563
- else :
564
- self .interface .update_view ()
565
- await interaction .response .edit_message (** self .interface .send_kwargs )
575
+ def __init__ (self , interface : 'PaginatorInterface' , * args : typing .Any , ** kwargs : typing .Any ):
576
+ super ().__init__ (* args , timeout = interface .timeout_length , ** kwargs )
577
+ self .interface = interface
578
+ self .page_number .label = f"Page number (1-{ interface .page_count } )"
579
+ self .page_number .min_length = 1
580
+ self .page_number .max_length = len (str (interface .page_count ))
566
581
567
- @ui .button (label = "\N{RIGHTWARDS ARROW WITH HOOK} \u200b Go to page" , style = discord .ButtonStyle .primary )
568
- async def button_goto (self , a : MaybeButton ['PaginatorInterface' ], b : MaybeButton ['PaginatorInterface' ]): # pylint: disable=unused-argument
569
- """Button to jump directly to a page"""
582
+ async def on_submit (self , interaction : discord .Interaction , / ):
583
+ try :
584
+ if not self .page_number .value :
585
+ raise ValueError ("Page number not filled" )
586
+
587
+ self .interface .display_page = int (self .page_number .value ) - 1
588
+ except ValueError :
589
+ await interaction .response .send_message (
590
+ content = f"``{ self .page_number .value } `` could not be converted to a page number" ,
591
+ ephemeral = True
592
+ )
593
+ else :
594
+ await interaction .response .edit_message (** self .interface .send_kwargs )
570
595
571
- interaction , _ = button_either_arg (a , b )
596
+ async def button_goto_callback (self , interaction : discord .Interaction ): # pylint: disable=unused-argument
597
+ """Button to jump directly to a page"""
572
598
573
- await interaction .response .send_modal (self .PageChangeModal (self ))
599
+ await interaction .response .send_modal (self .PageChangeModal (self ))
574
600
575
- @ui .button (label = "\N{BLACK SQUARE FOR STOP} \u200b Close paginator" , style = discord .ButtonStyle .danger )
576
- async def button_close (self , a : MaybeButton ['PaginatorInterface' ], b : MaybeButton ['PaginatorInterface' ]): # pylint: disable=unused-argument
601
+ def button_goto_label (self , _button : ui .Button [typing .Self ]) -> str :
602
+ return "\N{RIGHTWARDS ARROW WITH HOOK} \u200b Go to page"
603
+
604
+ async def button_close_callback (self , interaction : discord .Interaction ): # pylint: disable=unused-argument
577
605
"""Button to close the interface"""
578
606
579
607
message = self .message
@@ -584,6 +612,9 @@ async def button_close(self, a: MaybeButton['PaginatorInterface'], b: MaybeButto
584
612
if message :
585
613
await message .delete ()
586
614
615
+ def button_close_label (self , _button : ui .Button [typing .Self ]) -> str :
616
+ return f"{ self .emojis .close } \u200b Close paginator"
617
+
587
618
588
619
class PaginatorEmbedInterface (PaginatorInterface ):
589
620
"""
0 commit comments