""" Defines widgets for displaying a campaign's rewards. """ from typing import Optional, TYPE_CHECKING import asyncio import datetime as dt import discord from discord.ui import Modal from discord.ui.select import select, Select, SelectOption from discord.ui.button import button, Button, ButtonStyle from discord.ui.text_input import TextInput, TextStyle from meta import LionBot from meta.errors import UserInputError from meta.config import conf from utils.lib import tabulate, utc_now, MessageArgs, parse_duration from utils.ui import MessageUI, AButton, AsComponents, ConfigEditor from utils.ui.micros import FastModal from utils.ui.pagers import BasePager, Pager from ...campaign import RewardCampaign from ...data import EarnedReward from .. import logger class RewardEditor(FastModal): # Title is the reward we are editing # Block of text with dates and user info blurb = discord.ui.TextDisplay(content='placeholder') # Fulfilled is a checkbox, and supports a fulfilled note flufbox = discord.ui.Label( text="Fulfilled", description="Whether this reward has been completed", component=discord.ui.Checkbox( default=False ) ) # Reference and further notes need to be editable, # and need to set the default text correctly reference = discord.ui.Label( text="Reference", description="Reference URL or other information", component=discord.ui.TextInput( style=discord.TextStyle.long, ), ) notes = discord.ui.Label( text="Notes", description="Further notes for this user/reward", component=discord.ui.TextInput( style=discord.TextStyle.long, ), ) @classmethod def from_reward(cls, reward: EarnedReward): title = f"Reward #{reward.earned_id} for {reward.twitch_user_name or reward.twitch_user_id}" self = cls(title=title) # blurb table = { 'Earned At': discord.utils.format_dt(reward.earned_at, 'F'), 'Earned From': reward.earned_from, } prop_table = '\n'.join(tabulate(*table.items())) self.blurb.content = prop_table # Fulfilled self.flufbox.component.default = (reward.fulfilled_at is not None) # Notes self.reference.component.default = reward.reference or '' self.notes.component.default = reward.modnote or '' return self class RewardList(MessageUI): blocklen = 10 def __init__(self, bot: LionBot, campaign: RewardCampaign, **kwargs): super().__init__(**kwargs) self.bot = bot self.campaign = campaign # UI state self.pagen = 0 self.pages = [] self.reward_blocks = [[]] self._rewards: list[EarnedReward] = [] # self._reward_viewer: Optional[RewardViewer] = None @property def page(self): self.pagen %= self.page_count return self.reward_blocks[self.pagen] @property def page_count(self): return len(self.reward_blocks) # ----- UI API ----- # ----- UI Components ----- @select( cls=Select, placeholder="Select reward to edit", min_values=0, max_values=1, ) async def reward_menu(self, selection: discord.Interaction, selected): """ Select reward to view and edit. Maybe just edit for a start? """ if selected.values: # Hopefully this is a list of reminderids values = selected.values # Create detail widget, add to children # Spawn editor modal for this reward, callback to refresh detail widget # Also want to refresh this widget # Show detail widget as followup # TODO: Dedicated editor maybe, could e.g. embed refs. Maybe. value = int(selected.values[0]) reward = next(r for r in self._rewards if r.earned_id == value) modal = RewardEditor.from_reward(reward) await selection.response.send_modal(modal) await self.refresh() else: await selection.response.defer() async def reward_menu_refresh(self): menu = self.reward_menu rewards = self.page if rewards: menu.options = [ self._format_reward_option(r) for r in rewards ] menu.disabled = False else: menu.options = [ SelectOption(label='DUMMY') ] menu.disabled = True # Meta buttons @button(emoji=conf.emojis.getemoji("forward")) async def next_page_button(self, press: discord.Interaction, pressed): await press.response.defer() self.pagen += 1 await self.refresh() @button(emoji=conf.emojis.getemoji("backward")) async def prev_page_button(self, press: discord.Interaction, pressed): await press.response.defer() self.pagen -= 1 await self.refresh() @button(emoji=conf.emojis.cancel, style=ButtonStyle.red) async def quit_button(self, press: discord.Interaction, pressed: Button): """Close the UI and all children.""" await press.response.defer(thinking=False) await self.quit() @button(emoji=conf.emojis.refresh) async def refresh_button(self, press: discord.Interaction, pressed: Button): await press.response.defer() await self.refresh() # ----- UI Flow ----- def _format_reward_section(self, reward: EarnedReward) -> tuple[str, str]: """ Format the earned reward as an embed section. """ # Title is Reward #n earned by twitch_username # Reward, Earned at, Earned by, Fulfilled at (not fulfilled/date), Ref, Added Notes name = f"Reward #{reward.earned_id} earned by {reward.twitch_user_name or reward.twitch_user_id}" if reward.fulfilled_at is not None: fat = discord.utils.format_dt(reward.fulfilled_at, 'F') if reward.fulfilled_note is not None: fluf = f"{fat} ({reward.fulfilled_note})" else: fluf = fat else: fluf = "*Not Fulfilled*" table = { 'Reward': "Plus Campaign Sketch", 'Earned At': discord.utils.format_dt(reward.earned_at, 'F'), 'Earned From': reward.earned_from, 'Fulfilled At': fluf, 'Reference': "*No reference set*", 'Further notes': reward.modnote or "*No notes*", } prop_table = '\n'.join(tabulate(*table.items())) return (name, prop_table) def _format_reward_option(self, reward: EarnedReward) -> SelectOption: """ Format the earned reward as a selectable option """ # Reward #n earned by twitch_username # Value is the reward id name = f"Reward #{reward.earned_id} earned by {reward.twitch_user_name or reward.twitch_user_id}" value = reward.earned_id return SelectOption( label=name, value=value ) async def refresh_layout(self): to_refresh = ( self.reward_menu_refresh(), ) await asyncio.gather(*to_refresh) if self.page_count > 1: self.prev_page_button.disabled = True self.next_page_button.disabled = True else: self.prev_page_button.disabled = False self.next_page_button.disabled = False self.set_layout( (self.prev_page_button, self.refresh_button, self.next_page_button, self.quit_button), (self.reward_menu,), ) async def make_message(self) -> MessageArgs: embed = discord.Embed(title=f"{self.campaign.row.campaign_name} Reward List") # embed.set_footer with last update return MessageArgs(embed=embed) async def reload(self): rewards = self._rewards = await self.campaign.get_rewards() # TODO: Consider filter by unfulfilled or reward type self.reward_blocks = [ rewards[i:i+self.blocklen] for i in range(0, len(rewards), self.blocklen) ] or [[]]