Files
pluscampaign-plugin/plugin/discord/ui/rewards.py
T

255 lines
8.0 KiB
Python

"""
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 [[]]