todo pager and selector

This commit is contained in:
jellywx 2021-10-11 21:19:08 +01:00
parent ebabe0e85a
commit b310e99085
4 changed files with 136 additions and 64 deletions

View File

@ -6,7 +6,10 @@ use serenity::{
}; };
use crate::{ use crate::{
component_models::pager::TodoPager, component_models::{
pager::{Pager, TodoPager},
ComponentDataModel, TodoSelector,
},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
framework::{CommandInvoke, CommandOptions, CreateGenericResponse}, framework::{CommandInvoke, CommandOptions, CreateGenericResponse},
SQLPool, SQLPool,
@ -67,8 +70,6 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
_ => (Some(invoke.author_id().0), None, None), _ => (Some(invoke.author_id().0), None, None),
}; };
println!("{:?}", keys);
match args.get("task") { match args.get("task") {
Some(task) => { Some(task) => {
let task = task.to_string(); let task = task.to_string();
@ -91,7 +92,7 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
None => { None => {
let values = sqlx::query!( let values = sqlx::query!(
// fucking braindead mysql use <=> instead of = for null comparison // fucking braindead mysql use <=> instead of = for null comparison
"SELECT value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?", "SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?",
keys.0, keys.0,
keys.1, keys.1,
keys.2, keys.2,
@ -100,8 +101,8 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
.await .await
.unwrap() .unwrap()
.iter() .iter()
.map(|row| row.value.clone()) .map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<String>>(); .collect::<Vec<(usize, String)>>();
let resp = show_todo_page(&values, 0, keys.0, keys.1, keys.2); let resp = show_todo_page(&values, 0, keys.0, keys.1, keys.2);
@ -119,11 +120,11 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
} }
} }
pub fn max_todo_page(todo_values: &[String]) -> usize { pub fn max_todo_page(todo_values: &[(usize, String)]) -> usize {
let mut rows = 0; let mut rows = 0;
let mut char_count = 0; let mut char_count = 0;
todo_values.iter().enumerate().map(|(c, v)| format!("{}: {}", c, v)).fold( todo_values.iter().enumerate().map(|(c, (_, v))| format!("{}: {}", c, v)).fold(
1, 1,
|mut pages, text| { |mut pages, text| {
rows += 1; rows += 1;
@ -141,13 +142,13 @@ pub fn max_todo_page(todo_values: &[String]) -> usize {
} }
pub fn show_todo_page( pub fn show_todo_page(
todo_values: &[String], todo_values: &[(usize, String)],
page: usize, page: usize,
user_id: Option<u64>, user_id: Option<u64>,
channel_id: Option<u64>, channel_id: Option<u64>,
guild_id: Option<u64>, guild_id: Option<u64>,
) -> CreateInteractionResponse { ) -> CreateInteractionResponse {
// let pager = TodoPager::new(page, user_id, channel_id, guild_id); let pager = TodoPager::new(page, user_id, channel_id, guild_id);
let pages = max_todo_page(todo_values); let pages = max_todo_page(todo_values);
let mut page = page; let mut page = page;
@ -163,11 +164,11 @@ pub fn show_todo_page(
let mut skipped_pages = 0; let mut skipped_pages = 0;
let display_vec: Vec<String> = todo_values let (todo_ids, display_vec): (Vec<usize>, Vec<String>) = todo_values
.iter() .iter()
.enumerate() .enumerate()
.map(|(c, v)| format!("`{}`: {}", c + 1, v)) .map(|(c, (i, v))| (i, format!("`{}`: {}", c + 1, v)))
.skip_while(|p| { .skip_while(|(_, p)| {
first_num += 1; first_num += 1;
skipped_rows += 1; skipped_rows += 1;
skipped_char_count += p.len(); skipped_char_count += p.len();
@ -182,13 +183,13 @@ pub fn show_todo_page(
skipped_pages < page skipped_pages < page
}) })
.take_while(|p| { .take_while(|(_, p)| {
rows += 1; rows += 1;
char_count += p.len(); char_count += p.len();
char_count < EMBED_DESCRIPTION_MAX_LENGTH && rows <= SELECT_MAX_ENTRIES char_count < EMBED_DESCRIPTION_MAX_LENGTH && rows <= SELECT_MAX_ENTRIES
}) })
.collect(); .unzip();
let display = display_vec.join("\n"); let display = display_vec.join("\n");
@ -200,6 +201,9 @@ pub fn show_todo_page(
"Server" "Server"
}; };
let todo_selector =
ComponentDataModel::TodoSelector(TodoSelector { page, user_id, channel_id, guild_id });
let mut embed = CreateEmbed::default(); let mut embed = CreateEmbed::default();
embed embed
.title(format!("{} Todo List", title)) .title(format!("{} Todo List", title))
@ -208,7 +212,27 @@ pub fn show_todo_page(
.color(*THEME_COLOR); .color(*THEME_COLOR);
let mut response = CreateInteractionResponse::default(); let mut response = CreateInteractionResponse::default();
response.interaction_response_data(|d| d.embeds(vec![embed])); response.interaction_response_data(|d| {
d.embeds(vec![embed]).components(|comp| {
pager.create_button_row(pages, comp);
comp.create_action_row(|row| {
row.create_select_menu(|menu| {
menu.custom_id(todo_selector.to_custom_id()).options(|opt| {
for (count, (id, disp)) in todo_ids.iter().zip(&display_vec).enumerate() {
opt.create_option(|o| {
o.label(format!("Mark {} complete", count + first_num))
.value(id)
.description(disp.split_once(" ").unwrap_or(("", "")).1)
});
}
opt
})
})
})
})
});
response response
} }

View File

@ -17,7 +17,10 @@ use serenity::{
}; };
use crate::{ use crate::{
commands::reminder_cmds::{max_delete_page, show_delete_page}, commands::{
reminder_cmds::{max_delete_page, show_delete_page},
todo_cmds::{max_todo_page, show_todo_page},
},
component_models::pager::{DelPager, LookPager, Pager, TodoPager}, component_models::pager::{DelPager, LookPager, Pager, TodoPager},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
models::reminder::Reminder, models::reminder::Reminder,
@ -33,6 +36,7 @@ pub enum ComponentDataModel {
DelPager(DelPager), DelPager(DelPager),
TodoPager(TodoPager), TodoPager(TodoPager),
DelSelector(DelSelector), DelSelector(DelSelector),
TodoSelector(TodoSelector),
} }
impl ComponentDataModel { impl ComponentDataModel {
@ -199,7 +203,80 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
}) })
.await; .await;
} }
ComponentDataModel::TodoPager(pager) => {} ComponentDataModel::TodoPager(pager) => {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let values = sqlx::query!(
// fucking braindead mysql use <=> instead of = for null comparison
"SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?",
pager.user_id,
pager.channel_id,
pager.guild_id,
)
.fetch_all(&pool)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let max_pages = max_todo_page(&values);
let resp = show_todo_page(
&values,
pager.next_page(max_pages),
pager.user_id,
pager.channel_id,
pager.guild_id,
);
let _ = component
.create_interaction_response(&ctx, move |r| {
*r = resp;
r.kind(InteractionResponseType::UpdateMessage)
})
.await;
}
ComponentDataModel::TodoSelector(selector) => {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let selected_id = component.data.values.join(",");
sqlx::query!("DELETE FROM todos WHERE FIND_IN_SET(id, ?)", selected_id)
.execute(&pool)
.await
.unwrap();
let values = sqlx::query!(
// fucking braindead mysql use <=> instead of = for null comparison
"SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?",
selector.user_id,
selector.channel_id,
selector.guild_id,
)
.fetch_all(&pool)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let max_pages = max_todo_page(&values);
let resp = show_todo_page(
&values,
selector.page,
selector.user_id,
selector.channel_id,
selector.guild_id,
);
let _ = component
.create_interaction_response(&ctx, move |r| {
*r = resp;
r.kind(InteractionResponseType::UpdateMessage)
})
.await;
}
} }
} }
} }
@ -216,3 +293,11 @@ pub struct DelSelector {
pub page: usize, pub page: usize,
pub timezone: Tz, pub timezone: Tz,
} }
#[derive(Serialize, Deserialize)]
pub struct TodoSelector {
pub page: usize,
pub user_id: Option<u64>,
pub channel_id: Option<u64>,
pub guild_id: Option<u64>,
}

View File

@ -221,9 +221,9 @@ impl DelPager {
pub struct TodoPager { pub struct TodoPager {
pub page: usize, pub page: usize,
action: PageAction, action: PageAction,
pub user_id: Option<UserId>, pub user_id: Option<u64>,
pub channel_id: Option<ChannelId>, pub channel_id: Option<u64>,
pub guild_id: Option<GuildId>, pub guild_id: Option<u64>,
} }
impl Pager for TodoPager { impl Pager for TodoPager {
@ -278,18 +278,18 @@ impl Pager for TodoPager {
impl TodoPager { impl TodoPager {
pub fn new( pub fn new(
page: usize, page: usize,
user_id: Option<UserId>, user_id: Option<u64>,
channel_id: Option<ChannelId>, channel_id: Option<u64>,
guild_id: Option<GuildId>, guild_id: Option<u64>,
) -> Self { ) -> Self {
Self { page, action: PageAction::Refresh, user_id, channel_id, guild_id } Self { page, action: PageAction::Refresh, user_id, channel_id, guild_id }
} }
pub fn buttons( pub fn buttons(
page: usize, page: usize,
user_id: Option<UserId>, user_id: Option<u64>,
channel_id: Option<ChannelId>, channel_id: Option<u64>,
guild_id: Option<GuildId>, guild_id: Option<u64>,
) -> ( ) -> (
ComponentDataModel, ComponentDataModel,
ComponentDataModel, ComponentDataModel,

View File

@ -70,43 +70,6 @@ WHERE
.ok() .ok()
} }
pub async fn from_id(ctx: &Context, id: u32) -> Option<Self> {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
reminders.channel_id = channels.id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
reminders.id = ?
",
id
)
.fetch_one(&pool)
.await
.ok()
}
pub async fn from_channel<C: Into<ChannelId>>( pub async fn from_channel<C: Into<ChannelId>>(
ctx: &Context, ctx: &Context,
channel_id: C, channel_id: C,