From b310e99085b774b033b935704518ca00281a3a82 Mon Sep 17 00:00:00 2001 From: jellywx Date: Mon, 11 Oct 2021 21:19:08 +0100 Subject: [PATCH] todo pager and selector --- src/commands/todo_cmds.rs | 56 +++++++++++++++------- src/component_models/mod.rs | 89 ++++++++++++++++++++++++++++++++++- src/component_models/pager.rs | 18 +++---- src/models/reminder/mod.rs | 37 --------------- 4 files changed, 136 insertions(+), 64 deletions(-) diff --git a/src/commands/todo_cmds.rs b/src/commands/todo_cmds.rs index 6432608..e5499ff 100644 --- a/src/commands/todo_cmds.rs +++ b/src/commands/todo_cmds.rs @@ -6,7 +6,10 @@ use serenity::{ }; use crate::{ - component_models::pager::TodoPager, + component_models::{ + pager::{Pager, TodoPager}, + ComponentDataModel, TodoSelector, + }, consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR}, framework::{CommandInvoke, CommandOptions, CreateGenericResponse}, SQLPool, @@ -67,8 +70,6 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) { _ => (Some(invoke.author_id().0), None, None), }; - println!("{:?}", keys); - match args.get("task") { Some(task) => { let task = task.to_string(); @@ -91,7 +92,7 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) { None => { let values = sqlx::query!( // 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.1, keys.2, @@ -100,8 +101,8 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) { .await .unwrap() .iter() - .map(|row| row.value.clone()) - .collect::>(); + .map(|row| (row.id as usize, row.value.clone())) + .collect::>(); 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 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, |mut pages, text| { rows += 1; @@ -141,13 +142,13 @@ pub fn max_todo_page(todo_values: &[String]) -> usize { } pub fn show_todo_page( - todo_values: &[String], + todo_values: &[(usize, String)], page: usize, user_id: Option, channel_id: Option, guild_id: Option, ) -> 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 mut page = page; @@ -163,11 +164,11 @@ pub fn show_todo_page( let mut skipped_pages = 0; - let display_vec: Vec = todo_values + let (todo_ids, display_vec): (Vec, Vec) = todo_values .iter() .enumerate() - .map(|(c, v)| format!("`{}`: {}", c + 1, v)) - .skip_while(|p| { + .map(|(c, (i, v))| (i, format!("`{}`: {}", c + 1, v))) + .skip_while(|(_, p)| { first_num += 1; skipped_rows += 1; skipped_char_count += p.len(); @@ -182,13 +183,13 @@ pub fn show_todo_page( skipped_pages < page }) - .take_while(|p| { + .take_while(|(_, p)| { rows += 1; char_count += p.len(); char_count < EMBED_DESCRIPTION_MAX_LENGTH && rows <= SELECT_MAX_ENTRIES }) - .collect(); + .unzip(); let display = display_vec.join("\n"); @@ -200,6 +201,9 @@ pub fn show_todo_page( "Server" }; + let todo_selector = + ComponentDataModel::TodoSelector(TodoSelector { page, user_id, channel_id, guild_id }); + let mut embed = CreateEmbed::default(); embed .title(format!("{} Todo List", title)) @@ -208,7 +212,27 @@ pub fn show_todo_page( .color(*THEME_COLOR); 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 } diff --git a/src/component_models/mod.rs b/src/component_models/mod.rs index 31eba86..2ecebb2 100644 --- a/src/component_models/mod.rs +++ b/src/component_models/mod.rs @@ -17,7 +17,10 @@ use serenity::{ }; 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}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, models::reminder::Reminder, @@ -33,6 +36,7 @@ pub enum ComponentDataModel { DelPager(DelPager), TodoPager(TodoPager), DelSelector(DelSelector), + TodoSelector(TodoSelector), } impl ComponentDataModel { @@ -199,7 +203,80 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id }) .await; } - ComponentDataModel::TodoPager(pager) => {} + ComponentDataModel::TodoPager(pager) => { + let pool = ctx.data.read().await.get::().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::>(); + + 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::().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::>(); + + 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 timezone: Tz, } + +#[derive(Serialize, Deserialize)] +pub struct TodoSelector { + pub page: usize, + pub user_id: Option, + pub channel_id: Option, + pub guild_id: Option, +} diff --git a/src/component_models/pager.rs b/src/component_models/pager.rs index 172ba96..6344ced 100644 --- a/src/component_models/pager.rs +++ b/src/component_models/pager.rs @@ -221,9 +221,9 @@ impl DelPager { pub struct TodoPager { pub page: usize, action: PageAction, - pub user_id: Option, - pub channel_id: Option, - pub guild_id: Option, + pub user_id: Option, + pub channel_id: Option, + pub guild_id: Option, } impl Pager for TodoPager { @@ -278,18 +278,18 @@ impl Pager for TodoPager { impl TodoPager { pub fn new( page: usize, - user_id: Option, - channel_id: Option, - guild_id: Option, + user_id: Option, + channel_id: Option, + guild_id: Option, ) -> Self { Self { page, action: PageAction::Refresh, user_id, channel_id, guild_id } } pub fn buttons( page: usize, - user_id: Option, - channel_id: Option, - guild_id: Option, + user_id: Option, + channel_id: Option, + guild_id: Option, ) -> ( ComponentDataModel, ComponentDataModel, diff --git a/src/models/reminder/mod.rs b/src/models/reminder/mod.rs index 2ac11f6..573e8ca 100644 --- a/src/models/reminder/mod.rs +++ b/src/models/reminder/mod.rs @@ -70,43 +70,6 @@ WHERE .ok() } - pub async fn from_id(ctx: &Context, id: u32) -> Option { - let pool = ctx.data.read().await.get::().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>( ctx: &Context, channel_id: C,