import asyncio import discord from discord.ui.select import select, Select from discord.components import SelectOption from discord.enums import ButtonStyle, TextStyle from discord.ui.button import button, Button from discord.ui.text_input import TextInput from meta import LionBot, conf from meta.errors import SafeCancellation from utils.ui import MessageUI, input from utils.lib import MessageArgs from .. import logger from ...quotes import QuoteRegistry from ...lib import minify class QuoteListUI(MessageUI): block_len = 10 def __init__(self, bot: LionBot, quotes: QuoteRegistry, communityid: int, profileid: int, callerid: int, **kwargs): super().__init__(callerid=callerid, **kwargs) self.bot = bot self.quotes = quotes self.communityid = communityid self.caller_profileid = profileid # Paging state self._pagen = 0 self.blocks = [[]] @property def page_count(self): return len(self.blocks) @property def pagen(self): self._pagen = self._pagen % self.page_count return self._pagen @pagen.setter def pagen(self, value): self._pagen = value % self.page_count @property def current_page(self): return self.blocks[self.pagen] # ----- API ----- # ----- UI Components ----- @button( label="New Quote", style=ButtonStyle.green ) async def new_quote_button(self, press: discord.Interaction, pressed: Button): # Show the create quote modal # Create quote flow. try: interaction, to_create = await input( interaction=press, title="Create Quote", question="Quote Content", timeout=300, style=TextStyle.long, min_length=1, max_length=2000 - 8 ) except asyncio.TimeoutError: raise SafeCancellation await interaction.response.defer(thinking=True, ephemeral=True) quote = await self.quotes.create_quote( communityid=self.communityid, content=to_create, created_by=self.caller_profileid ) self.pagen = -1 await self.refresh(thinking=interaction) # Quote edit and delete selectors async def make_page_options(self): options = [] for quote in self.current_page: labelstr = f"#{quote.quotelabel}: " minified = minify(quote.content, 100 - len(labelstr) - 1, strip=' >') option = SelectOption( label=f"{labelstr} {minified}", value=str(quote.quoteid) ) options.append(option) return options @select( cls=Select, placeholder="Select to delete", min_values=1, max_values=1 ) async def delete_menu(self, selection: discord.Interaction, selected: Select): await selection.response.defer(thinking=True, ephemeral=True) if selected.values: quoteid = int(selected.values[0]) await self.quotes.delete_quote(quoteid) await self.refresh(thinking=selection) async def refresh_delete_menu(self): self.delete_menu.options = await self.make_page_options() @select( cls=Select, placeholder="Select to edit", min_values=1, max_values=1 ) async def edit_menu(self, selection: discord.Interaction, selected: Select): if selected.values: quoteid = int(selected.values[0]) quote = await self.quotes.get_quote(quoteid) assert quote is not None try: interaction, new_content = await input( interaction=selection, title="Edit Quote", question="Quote Content", timeout=300, style=TextStyle.long, default=quote.content, min_length=1, max_length=2000 - 8 ) await interaction.response.defer(thinking=True, ephemeral=True) except asyncio.TimeoutError: raise SafeCancellation await quote.update(content=new_content) await self.refresh(thinking=interaction) async def refresh_edit_menu(self): self.edit_menu.options = await self.make_page_options() # Backwards @button(emoji=conf.emojis.backward, style=ButtonStyle.grey) async def prev_button(self, press: discord.Interaction, pressed: Button): await press.response.defer(thinking=True, ephemeral=True) self.pagen -= 1 await self.refresh(thinking=press) # Jump to page @button(label="JUMP_PLACEHOLDER", style=ButtonStyle.blurple) async def jump_button(self, press: discord.Interaction, pressed: Button): """ Jump-to-page button. Loads a page-switch dialogue. """ t = self.bot.translator.t try: interaction, value = await input( press, title="Jump to page", question="Page number to jump to" ) value = value.strip() except asyncio.TimeoutError: return if not value.lstrip('- ').isdigit(): error_embed = discord.Embed( title="Invalid page number, please try again!", colour=discord.Colour.brand_red() ) await interaction.response.send_message(embed=error_embed, ephemeral=True) else: await interaction.response.defer(thinking=True) pagen = int(value.lstrip('- ')) if value.startswith('-'): pagen = -1 * pagen elif pagen > 0: pagen = pagen - 1 self.pagen = pagen await self.refresh(thinking=interaction) async def jump_button_refresh(self): component = self.jump_button component.label = f"{self.pagen + 1}/{self.page_count}" component.disabled = (self.page_count <= 1) # Forward @button(emoji=conf.emojis.forward, style=ButtonStyle.grey) async def next_button(self, press: discord.Interaction, pressed: Button): await press.response.defer(thinking=True) self.pagen += 1 await self.refresh(thinking=press) # Quit @button(emoji=conf.emojis.cancel, style=ButtonStyle.red) async def quit_button(self, press: discord.Interaction, pressed: Button): """ Quit the UI. """ await press.response.defer() await self.quit() # ----- UI Flow ----- async def make_message(self) -> MessageArgs: if self.current_page: lines = [] for quote in self.current_page: labelstr = f"**#{quote.quotelabel}:** " minified = minify(quote.content, 100 - len(labelstr) - 1, strip=' >') lines.append(f"{labelstr} {minified}") embed = discord.Embed( title="Community Quotes", description='\n'.join(lines) ) else: embed = discord.Embed( title="Community Quotes", description="No quotes have been created! Use `/addquote` or the 'New Quote' button below to make a quote!" ) return MessageArgs(embed=embed) async def refresh_layout(self): # Refresh menus to_refresh = ( self.refresh_edit_menu(), self.refresh_delete_menu(), self.jump_button_refresh(), ) await asyncio.gather(*to_refresh) if self.page_count > 1: # Multiple page case # < Add Jump Close > # edit # delete self.set_layout( (self.prev_button, self.new_quote_button, self.jump_button, self.quit_button, self.next_button), (self.edit_menu,), (self.delete_menu,), ) elif self.current_page: # Single page case # Add Close # Edit # Delete self.set_layout( (self.new_quote_button, self.jump_button, self.quit_button), (self.edit_menu,), (self.delete_menu,), ) else: # Add Close self.set_layout( (self.new_quote_button, self.jump_button), ) async def reload(self): quotes = await self.quotes.get_community_quotes(self.communityid) blocks = [ quotes[i:i+self.block_len] for i in range(0, len(quotes), self.block_len) ] self.blocks = blocks or [[]]