generated from HoloTech/holotech-plugin-template
(discord): Rough reward list and editor UI
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
from .rewards import RewardList, RewardEditor
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
"""
|
||||||
|
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 [[]]
|
||||||
Reference in New Issue
Block a user