Add dropdown to /inventory command for quick navigation (#365)
All checks were successful
Deploy To Stage / build (push) Successful in 9s
Deploy To Stage / deploy (push) Successful in 16s

- Add ability to handle dropdown menus with the bot
- Add a dropdown to the inventoryhelper
- Add handler for the dropdown to navigate to that page

#344

Reviewed-on: #365
Reviewed-by: VylpesTester <tester@vylpes.com>
Co-authored-by: Ethan Lane <ethan@vylpes.com>
Co-committed-by: Ethan Lane <ethan@vylpes.com>
This commit is contained in:
Ethan Lane 2024-09-21 18:09:24 +01:00 committed by Vylpes
parent 5ebc5ff27c
commit 1762b525b2
11 changed files with 146 additions and 7 deletions

View file

@ -38,6 +38,7 @@ const client = new CoreClient([
Registry.RegisterCommands(); Registry.RegisterCommands();
Registry.RegisterButtonEvents(); Registry.RegisterButtonEvents();
Registry.RegisterStringDropdownEvents();
if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") { if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") {
console.log("Card directory not found, syncing..."); console.log("Card directory not found, syncing...");

View file

@ -34,7 +34,7 @@ export default class Inventory extends ButtonEvent {
await interaction.editReply({ await interaction.editReply({
files: [ embed.image ], files: [ embed.image ],
embeds: [ embed.embed ], embeds: [ embed.embed ],
components: [ embed.row ], components: [ embed.row1, embed.row2 ],
}); });
} catch (e) { } catch (e) {
AppLogger.LogError("Button/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`); AppLogger.LogError("Button/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`);

View file

@ -17,11 +17,14 @@ import AppLogger from "./appLogger";
import TimerHelper from "../helpers/TimerHelper"; import TimerHelper from "../helpers/TimerHelper";
import GiveCurrency from "../timers/GiveCurrency"; import GiveCurrency from "../timers/GiveCurrency";
import PurgeClaims from "../timers/PurgeClaims"; import PurgeClaims from "../timers/PurgeClaims";
import StringDropdownEventItem from "../contracts/StringDropdownEventItem";
import {StringDropdownEvent} from "../type/stringDropdownEvent";
export class CoreClient extends Client { export class CoreClient extends Client {
private static _commandItems: ICommandItem[]; private static _commandItems: ICommandItem[];
private static _eventExecutors: EventExecutors; private static _eventExecutors: EventExecutors;
private static _buttonEvents: IButtonEventItem[]; private static _buttonEvents: IButtonEventItem[];
private static _stringDropdowns: StringDropdownEventItem[];
private _events: Events; private _events: Events;
private _util: Util; private _util: Util;
@ -45,6 +48,10 @@ export class CoreClient extends Client {
return this._buttonEvents; return this._buttonEvents;
} }
public static get stringDropdowns(): StringDropdownEventItem[] {
return this._stringDropdowns;
}
constructor(intents: number[]) { constructor(intents: number[]) {
super({ intents: intents }); super({ intents: intents });
dotenv.config(); dotenv.config();
@ -59,6 +66,7 @@ export class CoreClient extends Client {
CoreClient._commandItems = []; CoreClient._commandItems = [];
CoreClient._buttonEvents = []; CoreClient._buttonEvents = [];
CoreClient._stringDropdowns = [];
this._events = new Events(); this._events = new Events();
this._util = new Util(); this._util = new Util();
@ -408,4 +416,19 @@ export class CoreClient extends Client {
AppLogger.LogVerbose("Client", `Registered Button Event: ${buttonId}`); AppLogger.LogVerbose("Client", `Registered Button Event: ${buttonId}`);
} }
} }
public static RegisterStringDropdownEvent(dropdownId: string, event: StringDropdownEvent, environment: Environment = Environment.All) {
const item: StringDropdownEventItem = {
DropdownId: dropdownId,
Event: event,
Environment: environment,
};
if ((environment & CoreClient.Environment) == CoreClient.Environment) {
CoreClient._stringDropdowns.push(item);
AppLogger.LogVerbose("Client", `Registered String Dropdown Event: ${dropdownId}`);
} }
}
}

View file

@ -3,6 +3,7 @@ import ChatInputCommand from "./interactionCreate/ChatInputCommand";
import Button from "./interactionCreate/Button"; import Button from "./interactionCreate/Button";
import AppLogger from "./appLogger"; import AppLogger from "./appLogger";
import NewUserDiscovery from "./interactionCreate/middleware/NewUserDiscovery"; import NewUserDiscovery from "./interactionCreate/middleware/NewUserDiscovery";
import StringDropdown from "./interactionCreate/StringDropdown";
export class Events { export class Events {
public async onInteractionCreate(interaction: Interaction) { public async onInteractionCreate(interaction: Interaction) {
@ -19,6 +20,11 @@ export class Events {
AppLogger.LogVerbose("Client", `Button: ${interaction.customId}`); AppLogger.LogVerbose("Client", `Button: ${interaction.customId}`);
Button.onButtonClicked(interaction); Button.onButtonClicked(interaction);
} }
if (interaction.isStringSelectMenu()) {
AppLogger.LogVerbose("Client", `StringDropdown: ${interaction.customId}`);
StringDropdown.onStringDropdownSelected(interaction);
}
} }
// Emit when bot is logged in and ready to use // Emit when bot is logged in and ready to use

View file

@ -0,0 +1,29 @@
import {StringSelectMenuInteraction} from "discord.js";
import {CoreClient} from "../client";
import AppLogger from "../appLogger";
export default class StringDropdown {
public static async onStringDropdownSelected(interaction: StringSelectMenuInteraction) {
if (!interaction.isStringSelectMenu()) return;
const item = CoreClient.stringDropdowns.find(x => x.DropdownId == interaction.customId.split(" ")[0]);
if (!item) {
AppLogger.LogVerbose("StringDropdown", `Event not found: ${interaction.customId}`);
await interaction.reply("Event not found");
return;
}
try {
AppLogger.LogDebug("StringDropdown", `Executing ${interaction.customId}`);
item.Event.execute(interaction);
} catch (e) {
AppLogger.LogError("StringDropdown", `Error occurred while executing event: ${interaction.customId}`);
AppLogger.LogError("StringDropdown", e as string);
await interaction.reply("An error occurred while executing the event");
}
}
}

View file

@ -47,7 +47,7 @@ export default class Inventory extends Command {
await interaction.followUp({ await interaction.followUp({
files: [ embed.image ], files: [ embed.image ],
embeds: [ embed.embed ], embeds: [ embed.embed ],
components: [ embed.row ], components: [ embed.row1, embed.row2 ],
}); });
} catch (e) { } catch (e) {
AppLogger.LogError("Commands/Inventory", e as string); AppLogger.LogError("Commands/Inventory", e as string);

View file

@ -0,0 +1,10 @@
import {Environment} from "../constants/Environment";
import {StringDropdownEvent} from "../type/stringDropdownEvent";
interface StringDropdownEventItem {
DropdownId: string,
Event: StringDropdownEvent,
Environment: Environment,
}
export default StringDropdownEventItem;

View file

@ -1,4 +1,4 @@
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from "discord.js";
import Inventory from "../database/entities/app/Inventory"; import Inventory from "../database/entities/app/Inventory";
import { CoreClient } from "../client/client"; import { CoreClient } from "../client/client";
import EmbedColours from "../constants/EmbedColours"; import EmbedColours from "../constants/EmbedColours";
@ -24,7 +24,8 @@ interface InventoryPageCards {
interface ReturnedInventoryPage { interface ReturnedInventoryPage {
embed: EmbedBuilder, embed: EmbedBuilder,
row: ActionRowBuilder<ButtonBuilder>, row1: ActionRowBuilder<ButtonBuilder>,
row2: ActionRowBuilder<StringSelectMenuBuilder>,
image: AttachmentBuilder, image: AttachmentBuilder,
} }
@ -99,7 +100,7 @@ export default class InventoryHelper {
.setColor(EmbedColours.Ok) .setColor(EmbedColours.Ok)
.setImage("attachment://page.png"); .setImage("attachment://page.png");
const row = new ActionRowBuilder<ButtonBuilder>() const row1 = new ActionRowBuilder<ButtonBuilder>()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId(`inventory ${userid} ${page - 1}`) .setCustomId(`inventory ${userid} ${page - 1}`)
@ -112,9 +113,23 @@ export default class InventoryHelper {
.setStyle(ButtonStyle.Primary) .setStyle(ButtonStyle.Primary)
.setDisabled(page + 1 == pages.length)); .setDisabled(page + 1 == pages.length));
let pageNum = 0;
const row2 = new ActionRowBuilder<StringSelectMenuBuilder>()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId("inventory")
.setPlaceholder(`${currentPage.name} (${currentPage.seriesSubpage + 1})`)
.addOptions(...pages.map(x =>
new StringSelectMenuOptionBuilder()
.setLabel(`${x.name} (${x.seriesSubpage + 1})`.substring(0, 100))
.setDescription(`Page ${pageNum + 1}`)
.setDefault(currentPage.id == x.id)
.setValue(`${userid} ${pageNum++}`))));
const buffer = await ImageHelper.GenerateCardImageGrid(currentPage.cards.map(x => ({ id: x.id, path: x.path }))); const buffer = await ImageHelper.GenerateCardImageGrid(currentPage.cards.map(x => ({ id: x.id, path: x.path })));
const image = new AttachmentBuilder(buffer, { name: "page.png" }); const image = new AttachmentBuilder(buffer, { name: "page.png" });
return { embed, row, image }; return { embed, row1, row2, image };
} }
} }

View file

@ -31,6 +31,9 @@ import SeriesEvent from "./buttonEvents/Series";
import TradeButtonEvent from "./buttonEvents/Trade"; import TradeButtonEvent from "./buttonEvents/Trade";
import ViewButtonEvent from "./buttonEvents/View"; import ViewButtonEvent from "./buttonEvents/View";
// String Dropdown Event Imports
import InventoryStringDropdown from "./stringDropdowns/Inventory";
export default class Registry { export default class Registry {
public static RegisterCommands() { public static RegisterCommands() {
// Global Commands // Global Commands
@ -64,4 +67,8 @@ export default class Registry {
CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent()); CoreClient.RegisterButtonEvent("trade", new TradeButtonEvent());
CoreClient.RegisterButtonEvent("view", new ViewButtonEvent()); CoreClient.RegisterButtonEvent("view", new ViewButtonEvent());
} }
public static RegisterStringDropdownEvents() {
CoreClient.RegisterStringDropdownEvent("inventory", new InventoryStringDropdown());
}
} }

View file

@ -0,0 +1,43 @@
import {StringSelectMenuInteraction} from "discord.js";
import {StringDropdownEvent} from "../type/stringDropdownEvent";
import AppLogger from "../client/appLogger";
import InventoryHelper from "../helpers/InventoryHelper";
export default class Inventory extends StringDropdownEvent {
public override async execute(interaction: StringSelectMenuInteraction) {
if (!interaction.guild) return;
const userid = interaction.values[0].split(" ")[0];
const page = interaction.values[0].split(" ")[1];
AppLogger.LogDebug("StringDropdown/Inventory", `Parameters: userid=${userid}, page=${page}`);
await interaction.deferUpdate();
const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid);
if (!member) {
await interaction.reply("Unable to find user.");
return;
}
try {
const embed = await InventoryHelper.GenerateInventoryPage(member.user.username, member.user.id, Number(page));
if (!embed) {
await interaction.followUp("No page for user found.");
return;
}
await interaction.editReply({
files: [ embed.image ],
embeds: [ embed.embed ],
components: [ embed.row1, embed.row2 ],
});
} catch (e) {
AppLogger.LogError("StringDropdown/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`);
await interaction.followUp("An error has occurred running this command.");
}
}
}

View file

@ -0,0 +1,5 @@
import {StringSelectMenuInteraction} from "discord.js";
export abstract class StringDropdownEvent {
abstract execute(interaction: StringSelectMenuInteraction): Promise<void>;
}