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 ...koans import KoanRegistry from ...lib import minify class KoanListUI(MessageUI): block_len = 10 def __init__(self, bot: LionBot, koans: KoanRegistry, communityid: int, profileid: int, callerid: int, **kwargs): super().__init__(callerid=callerid, **kwargs) self.bot = bot self.koans = koans 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 Koan", style=ButtonStyle.green ) async def new_koan_button(self, press: discord.Interaction, pressed: Button): # Show the create koan modal # Create koan flow. try: interaction, to_create = await input( interaction=press, title="Create Koan", question="Koan 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) koan = await self.koans.create_koan( communityid=self.communityid, content=to_create, created_by=self.caller_profileid ) self.pagen = -1 await self.refresh(thinking=interaction) # Koan edit and delete selectors async def make_page_options(self): options = [] for koan in self.current_page: labelstr = f"#{koan.koanlabel}: " minified = minify(koan.content, 100 - len(labelstr) - 1, strip=' >') option = SelectOption( label=f"{labelstr} {minified}", value=str(koan.koanid) ) 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: koanid = int(selected.values[0]) await self.koans.delete_koan(koanid) 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: koanid = int(selected.values[0]) koan = await self.koans.get_koan(koanid) assert koan is not None try: interaction, new_content = await input( interaction=selection, title="Edit Koan", question="Koan Content", timeout=300, style=TextStyle.long, default=koan.content, min_length=1, max_length=2000 - 8 ) await interaction.response.defer(thinking=True, ephemeral=True) except asyncio.TimeoutError: raise SafeCancellation await koan.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 koan in self.current_page: labelstr = f"**#{koan.koanlabel}:** " minified = minify(koan.content, 100 - len(labelstr) - 1, strip=' >') lines.append(f"{labelstr} {minified}") embed = discord.Embed( title="Community Koans", description='\n'.join(lines) ) else: embed = discord.Embed( title="Community Koans", description="No koans have been created! Use `/koans add` or the 'New Koan' button below to make a koan!" ) 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_koan_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_koan_button, self.quit_button), (self.edit_menu,), (self.delete_menu,), ) else: # Add Close self.set_layout( (self.new_koan_button, self.quit_button), ) async def reload(self): koans = await self.koans.get_community_koans(self.communityid) blocks = [ koans[i:i+self.block_len] for i in range(0, len(koans), self.block_len) ] self.blocks = blocks or [[]]