feat(polish): Adds EmbedLibrary

This commit is contained in:
Michel Fedde 2025-06-24 21:59:55 +02:00
parent cf9c88a2d6
commit b3d0b3a90c
12 changed files with 240 additions and 136 deletions

7
package-lock.json generated
View file

@ -14,6 +14,7 @@
"@types/lodash": "^4.17.18", "@types/lodash": "^4.17.18",
"@types/log4js": "^0.0.33", "@types/log4js": "^0.0.33",
"@types/node": "^22.13.9", "@types/node": "^22.13.9",
"any-date-parser": "^2.2.2",
"better-sqlite3": "^11.8.1", "better-sqlite3": "^11.8.1",
"deepmerge": "^4.3.1", "deepmerge": "^4.3.1",
"discord.js": "^14.18.0", "discord.js": "^14.18.0",
@ -2051,6 +2052,12 @@
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/any-date-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/any-date-parser/-/any-date-parser-2.2.2.tgz",
"integrity": "sha512-ZgitJ8kchTF57Hm1PrcX/WCD5ZliRdk+KmL1YKxfHq8a8Em5GpjYtkaw/ctt0kgBYG1q9hsncQIQNUwGDuhPzw==",
"license": "ISC"
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",

View file

@ -20,6 +20,7 @@
"@types/lodash": "^4.17.18", "@types/lodash": "^4.17.18",
"@types/log4js": "^0.0.33", "@types/log4js": "^0.0.33",
"@types/node": "^22.13.9", "@types/node": "^22.13.9",
"any-date-parser": "^2.2.2",
"better-sqlite3": "^11.8.1", "better-sqlite3": "^11.8.1",
"deepmerge": "^4.3.1", "deepmerge": "^4.3.1",
"discord.js": "^14.18.0", "discord.js": "^14.18.0",

View file

@ -35,20 +35,13 @@ export class PlaydateRepository extends Repository<PlaydateModel, DBPlaydate> {
return finds.map((playdate) => this.convertToModelType(playdate, group)); return finds.map((playdate) => this.convertToModelType(playdate, group));
} }
findPlaydatesInRange(fromDate: Date | number, toDate: Date | number | undefined = undefined, group: GroupModel | undefined = undefined) { findPlaydatesInRange(fromDate: Date, toDate: Date | undefined = undefined, group: GroupModel | undefined = undefined) {
if (fromDate instanceof Date) {
fromDate = fromDate.getTime();
}
if (toDate instanceof Date) {
toDate = toDate.getTime();
}
let sql = `SELECT * FROM ${this.schema.name} WHERE time_from > ?`; let sql = `SELECT * FROM ${this.schema.name} WHERE time_from > ?`;
const params = [fromDate]; const params = [fromDate.getTime()];
if (toDate) { if (toDate) {
sql = `${sql} AND time_from < ?` sql = `${sql} AND time_from < ?`
params.push(toDate); params.push(toDate.getTime());
} }
if (group) { if (group) {

View file

@ -5,9 +5,11 @@ import {
GuildMember, GuildMember,
GuildMemberRoleManager, GuildMemberRoleManager,
InteractionReplyOptions, InteractionReplyOptions,
MessageFlags, PermissionFlagsBits, MessageFlags,
PermissionFlagsBits,
roleMention, roleMention,
SlashCommandBuilder, Snowflake, SlashCommandBuilder,
Snowflake,
time, time,
userMention userMention
} from "discord.js"; } from "discord.js";
@ -23,7 +25,6 @@ import {PlaydateRepository} from "../../Database/Repositories/PlaydateRepository
import {Nullable} from "../../types/Nullable"; import {Nullable} from "../../types/Nullable";
import {MenuRenderer} from "../../Menu/MenuRenderer"; import {MenuRenderer} from "../../Menu/MenuRenderer";
import {MenuItemType} from "../../Menu/MenuRenderer.types"; import {MenuItemType} from "../../Menu/MenuRenderer.types";
import {ConfigurationMenuHandler} from "../../Configuration/Groups/ConfigurationMenuHandler";
import {MenuTraversal} from "../../Menu/MenuTraversal"; import {MenuTraversal} from "../../Menu/MenuTraversal";
import {ConfigurationHandler} from "../../Configuration/ConfigurationHandler"; import {ConfigurationHandler} from "../../Configuration/ConfigurationHandler";
import {GroupConfigurationProvider} from "../../Configuration/Groups/GroupConfigurationProvider"; import {GroupConfigurationProvider} from "../../Configuration/Groups/GroupConfigurationProvider";
@ -31,6 +32,7 @@ import {MenuHandler} from "../../Configuration/MenuHandler";
import {ServerConfigurationProvider} from "../../Configuration/Server/ServerConfigurationProvider"; import {ServerConfigurationProvider} from "../../Configuration/Server/ServerConfigurationProvider";
import {ServerConfigurationRepository} from "../../Database/Repositories/ServerConfigurationRepository"; import {ServerConfigurationRepository} from "../../Database/Repositories/ServerConfigurationRepository";
import {PermissionError} from "../PermissionError"; import {PermissionError} from "../PermissionError";
import {EmbedLibrary, EmbedType} from "../EmbedLibrary";
export class GroupCommand implements Command, ChatInteractionCommand, AutocompleteCommand { export class GroupCommand implements Command, ChatInteractionCommand, AutocompleteCommand {
private static GOODBYE_MESSAGES: string[] = [ private static GOODBYE_MESSAGES: string[] = [
@ -155,7 +157,16 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
Container.get<GroupRepository>(GroupRepository.name).create(group); Container.get<GroupRepository>(GroupRepository.name).create(group);
interaction.reply({content: `:white_check_mark: Created group \`${name}\``, flags: MessageFlags.Ephemeral}) interaction.reply({
embeds: [
EmbedLibrary.base(
'Created group',
`:white_check_mark: Created group \`${name}\``,
EmbedType.Success
)
],
flags: MessageFlags.Ephemeral
})
} }
private allowedCreate(interaction: ChatInputCommandInteraction): boolean { private allowedCreate(interaction: ChatInputCommandInteraction): boolean {
@ -192,8 +203,7 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
const playdateRepo = Container.get<PlaydateRepository>(PlaydateRepository.name); const playdateRepo = Container.get<PlaydateRepository>(PlaydateRepository.name);
const embed = new EmbedBuilder() const embed = EmbedLibrary.base("Your groups on this server:", '', EmbedType.Info)
.setTitle("Your groups on this server:")
.setFields( .setFields(
groups.map(group => { groups.map(group => {
const nextPlaydate = playdateRepo.getNextPlaydateForGroup(group); const nextPlaydate = playdateRepo.getNextPlaydateForGroup(group);
@ -237,15 +247,13 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
repo.deleteGroup(group); repo.deleteGroup(group);
const embed = new EmbedBuilder()
.setTitle("Group deleted.")
.setDescription(
`:x: Deleted \`${group.name}\`. ${ArrayUtils.chooseRandom(GroupCommand.GOODBYE_MESSAGES)}`
)
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
embed EmbedLibrary.base(
"Group deleted",
`:x: Deleted \`${group.name}\`. ${ArrayUtils.chooseRandom(GroupCommand.GOODBYE_MESSAGES)}`,
EmbedType.Success
)
], ],
flags: MessageFlags.Ephemeral, flags: MessageFlags.Ephemeral,
}) })
@ -332,7 +340,9 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
), ),
'Group Configuration', 'Group Configuration',
"This UI allows you to change settings for your group." "This UI allows you to change settings for your group."
) ),
null,null,
group.name
) )
menu.display(interaction); menu.display(interaction);
@ -359,16 +369,13 @@ export class GroupCommand implements Command, ChatInteractionCommand, Autocomple
group.leader.memberid = newLeader.id group.leader.memberid = newLeader.id
repo.update(group); repo.update(group);
const embed = new EmbedBuilder()
.setTitle("Leadership transferred")
.setDescription(
`Leadership was successfully transferred to ${userMention(newLeader.user.id)}`
)
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
embed EmbedLibrary.base(
'Leadership transferred',
`Leadership was successfully transferred to ${userMention(newLeader.user.id)}`,
EmbedType.Success
)
], ],
flags: MessageFlags.Ephemeral, flags: MessageFlags.Ephemeral,
}) })

View file

@ -1,13 +1,13 @@
import { import {
SlashCommandBuilder,
CommandInteraction,
AutocompleteInteraction,
EmbedBuilder,
MessageFlags,
ChatInputCommandInteraction,
time,
AttachmentBuilder, AttachmentBuilder,
GuildMember AutocompleteInteraction,
ChatInputCommandInteraction,
CommandInteraction,
EmbedBuilder,
GuildMember,
MessageFlags,
SlashCommandBuilder,
time
} from "discord.js"; } from "discord.js";
import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command"; import {AutocompleteCommand, ChatInteractionCommand, Command} from "./Command";
import {Container} from "../../Container/Container"; import {Container} from "../../Container/Container";
@ -20,9 +20,15 @@ import * as ics from 'ics';
import ical from 'node-ical'; import ical from 'node-ical';
import {GroupConfigurationRepository} from "../../Database/Repositories/GroupConfigurationRepository"; import {GroupConfigurationRepository} from "../../Database/Repositories/GroupConfigurationRepository";
import {GroupRepository} from "../../Database/Repositories/GroupRepository"; import {GroupRepository} from "../../Database/Repositories/GroupRepository";
import {GroupConfigurationProvider} from "../../Configuration/Groups/GroupConfigurationProvider"; import {
import { ConfigurationHandler } from "../../Configuration/ConfigurationHandler"; GroupConfigurationProvider,
RuntimeGroupConfiguration
} from "../../Configuration/Groups/GroupConfigurationProvider";
import {ConfigurationHandler} from "../../Configuration/ConfigurationHandler";
import {PermissionError} from "../PermissionError"; import {PermissionError} from "../PermissionError";
import {EmbedLibrary, EmbedType} from "../EmbedLibrary";
import {GroupConfigurationModel} from "../../Database/Models/GroupConfigurationModel";
import parser from "any-date-parser";
export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInteractionCommand { export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInteractionCommand {
definition(): SlashCommandBuilder { definition(): SlashCommandBuilder {
@ -36,11 +42,11 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
.addIntegerOption(GroupSelection.createOptionSetup()) .addIntegerOption(GroupSelection.createOptionSetup())
.addStringOption((option) => option .addStringOption((option) => option
.setName("from") .setName("from")
.setDescription("Defines the start date & time. Format: YYYY-MM-DD HH:mm") .setDescription("Defines the start date & time. Your desired format is probably support.")
) )
.addStringOption((option) => option .addStringOption((option) => option
.setName("to") .setName("to")
.setDescription("Defines the end date & time. Format: YYYY-MM-DD HH:mm") .setDescription("Defines the end date & time. Your desired format is probably support.")
) )
) )
.addSubcommand((subcommand) => subcommand .addSubcommand((subcommand) => subcommand
@ -117,18 +123,18 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
) )
} }
const fromDate = Date.parse(<string>interaction.options.get("from")?.value ?? ''); const fromDate = parser.fromString(<string>interaction.options.get("from")?.value ?? '');
const toDate = Date.parse(<string>interaction.options.get("to")?.value ?? ''); const toDate = parser.fromString(<string>interaction.options.get("to")?.value ?? '');
if (isNaN(fromDate)) { if (!fromDate.isValid()) {
throw new UserError("No date or invalid date format for the from parameter."); throw new UserError("No date or invalid date format for the from parameter.");
} }
if (isNaN(toDate)) { if (!fromDate.isValid()) {
throw new UserError("No date or invalid date format for the to parameter."); throw new UserError("No date or invalid date format for the to parameter.");
} }
if (fromDate > toDate) { if (fromDate.getTime() > toDate.getTime()) {
throw new UserError("The to-date can't be earlier than the from-date"); throw new UserError("The to-date can't be earlier than the from-date");
} }
@ -141,22 +147,21 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
const playdate: Partial<PlaydateModel> = { const playdate: Partial<PlaydateModel> = {
group: group, group: group,
from_time: new Date(fromDate), from_time: fromDate,
to_time: new Date(toDate), to_time: toDate,
} }
playdateRepo.create(playdate); playdateRepo.create(playdate);
const embed = new EmbedBuilder() const embed = EmbedLibrary.playdate(
.setTitle("Created a play-date.") group,
.setDescription(":white_check_mark: Your playdate has been created! You and your group get notified, when its time.") "Created a play-date.",
.setFields({ ":white_check_mark: Your playdate has been created! You and your group get notified, when its time.",
EmbedType.Success
).setFields({
name: "Created playdate", name: "Created playdate",
value: `${time(new Date(fromDate), 'F')} - ${time(new Date(toDate), 'F')}`, value: `${time(new Date(fromDate), 'F')} - ${time(new Date(toDate), 'F')}`,
}) })
.setFooter({
text: `Group: ${group.name}`
})
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
@ -193,9 +198,12 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
private async list(interaction: ChatInputCommandInteraction, group: GroupModel) { private async list(interaction: ChatInputCommandInteraction, group: GroupModel) {
const playdates = Container.get<PlaydateRepository>(PlaydateRepository.name).findFromGroup(group); const playdates = Container.get<PlaydateRepository>(PlaydateRepository.name).findFromGroup(group);
const embed = new EmbedBuilder() const embed = EmbedLibrary.playdate(
.setTitle("The next playdates:") group,
.setFields( "Created a play-date.",
null,
EmbedType.Info
).setFields(
playdates.map((playdate) => { playdates.map((playdate) => {
return { return {
name: `${time(playdate.from_time, 'F')} - ${time(playdate.to_time, 'F')}`, name: `${time(playdate.from_time, 'F')} - ${time(playdate.to_time, 'F')}`,
@ -203,9 +211,6 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
} }
}) })
) )
.setFooter({
text: `Group: ${group.name}`
})
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
@ -237,15 +242,12 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
repo.delete(playdateId); repo.delete(playdateId);
const embed = new EmbedBuilder() const embed = EmbedLibrary.playdate(
.setTitle("Playdate deleted") group,
.setDescription( "Playdate deleted",
`:x: Deleted \`${selected.from_time.toLocaleString()} - ${selected.to_time.toLocaleString()}\`` `:x: Deleted ${time(selected.from_time, 'F')} - ${time(selected.to_time, 'F')}`,
) EmbedType.Success
.setFooter({ );
text: `Group: ${group.name}`
})
await interaction.reply({ await interaction.reply({
embeds: [ embeds: [
embed embed
@ -294,16 +296,15 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
}); });
} }
const embed = new EmbedBuilder() const embed = EmbedLibrary.playdate(
.setTitle("Imported play-dates.") group,
.setDescription(`:white_check_mark: Your ${playdates.length} playdates has been created! You and your group get notified, when its time.`) "Imported play-dates",
.setFields({ `:white_check_mark: Your ${playdates.length} playdates has been created! You and your group get notified, when its time.`,
EmbedType.Success
).setFields({
name: "Created playdates", name: "Created playdates",
value: playdates.map((playdate) => `${time(playdate.from_time, 'F')} - ${time(playdate.to_time, 'F')}`).join('\n') value: playdates.map((playdate) => `${time(playdate.from_time, 'F')} - ${time(playdate.to_time, 'F')}`).join('\n')
}) })
.setFooter({
text: `Group: ${group.name}`
})
interaction.followUp({ interaction.followUp({
embeds: [embed], embeds: [embed],
@ -312,7 +313,10 @@ export class PlaydatesCommand implements Command, AutocompleteCommand, ChatInter
} }
private async export(interaction: ChatInputCommandInteraction, group: GroupModel): Promise<void> { private async export(interaction: ChatInputCommandInteraction, group: GroupModel): Promise<void> {
const groupConfig = new ConfigurationHandler( const groupConfig = new ConfigurationHandler<
GroupConfigurationModel,
RuntimeGroupConfiguration
>(
new GroupConfigurationProvider( new GroupConfigurationProvider(
Container.get<GroupConfigurationRepository>(GroupConfigurationRepository.name), Container.get<GroupConfigurationRepository>(GroupConfigurationRepository.name),
group group

View file

@ -60,7 +60,7 @@ export class ServerCommand implements Command, ChatInteractionCommand {
children: [ children: [
{ {
traversalKey: "allowEveryone", traversalKey: "allowEveryone",
label: "Group Creation", label: "Allow Anyone",
description: "Defines if all members are allowed to create groups.", description: "Defines if all members are allowed to create groups.",
} }
] ]
@ -71,7 +71,10 @@ export class ServerCommand implements Command, ChatInteractionCommand {
), ),
'Server Configuration', 'Server Configuration',
"This UI allows you to change settings for your server." "This UI allows you to change settings for your server."
) ),
null,
null,
"Server"
) )
menu.display(interaction); menu.display(interaction);

View file

@ -0,0 +1,64 @@
import {ColorResolvable, Colors, EmbedBuilder} from "discord.js";
import {ArrayUtils} from "../Utilities/ArrayUtils";
import {GroupModel} from "../Database/Models/GroupModel";
export enum EmbedType {
Unknown,
Success,
Info,
Error,
}
export class EmbedLibrary {
private static TYPE_COLOR_MAP: Record<EmbedType, ColorResolvable> = {
[EmbedType.Unknown]: Colors.Yellow,
[EmbedType.Success]: Colors.Green,
[EmbedType.Info]: Colors.Blue,
[EmbedType.Error]: Colors.Red,
}
public static base(
title: string,
description: string|null = null,
type: EmbedType = EmbedType.Unknown
): EmbedBuilder {
const embed = new EmbedBuilder()
.setTitle(title)
.setColor(this.TYPE_COLOR_MAP[type])
if (description) {
embed.setDescription(description);
}
return embed
}
public static playdate(
group: GroupModel,
title: string,
description: string|null = null,
type: EmbedType = EmbedType.Unknown
): EmbedBuilder {
return this.base(title, description, type)
.setFooter({
text: `Group: ${group.name}`
})
}
public static error(
error: Error
): EmbedBuilder {
if ("getEmbed" in error) {
return (<(error: Error) => EmbedBuilder>error.getEmbed)(error);
}
return this.base(
"A unexpected error occurred",
":x: There was an error while executing this command!",
EmbedType.Error
).setFooter({
text: "Type: Generic"
})
}
}

View file

@ -3,7 +3,7 @@ import {
AutocompleteInteraction, ButtonInteraction, AutocompleteInteraction, ButtonInteraction,
ChatInputCommandInteraction, ChatInputCommandInteraction,
inlineCode, inlineCode,
Interaction, Interaction, InteractionReplyOptions,
MessageFlags, ModalSubmitInteraction, MessageFlags, ModalSubmitInteraction,
} from "discord.js"; } from "discord.js";
import Commands from "./Commands/Commands"; import Commands from "./Commands/Commands";
@ -15,6 +15,7 @@ import {ModalInteractionEvent} from "../Events/EventClasses/ModalInteractionEven
import {ComponentInteractionEvent} from "../Events/EventClasses/ComponentInteractionEvent"; import {ComponentInteractionEvent} from "../Events/EventClasses/ComponentInteractionEvent";
import {log} from "node:util"; import {log} from "node:util";
import {PermissionError} from "./PermissionError"; import {PermissionError} from "./PermissionError";
import {EmbedLibrary} from "./EmbedLibrary";
enum InteractionRoutingType { enum InteractionRoutingType {
Unrouted, Unrouted,
@ -94,12 +95,8 @@ export class InteractionRouter {
await command.execute?.call(command, interaction); await command.execute?.call(command, interaction);
} catch (e: any) { } catch (e: any) {
let userMessage = ":x: There was an error while executing this command!";
let logErrorMessage = true;
if ("getDiscordMessage" in e) { let logErrorMessage = true;
userMessage = e.getDiscordMessage(e);
}
if ("shouldLog" in e) { if ("shouldLog" in e) {
logErrorMessage = e.shouldLog; logErrorMessage = e.shouldLog;
} }
@ -108,10 +105,17 @@ export class InteractionRouter {
this.logger.error(e) this.logger.error(e)
} }
const responseOptions: InteractionReplyOptions = {
embeds: [
EmbedLibrary.error(e)
],
flags: MessageFlags.Ephemeral
}
if (interaction.replied || interaction.deferred) { if (interaction.replied || interaction.deferred) {
await interaction.followUp({content: userMessage, flags: MessageFlags.Ephemeral}); await interaction.followUp(responseOptions);
} else { } else {
await interaction.reply({content: userMessage, flags: MessageFlags.Ephemeral}); await interaction.reply(responseOptions);
} }
} }
} }

View file

@ -1,4 +1,5 @@
import {inlineCode} from "discord.js"; import {EmbedBuilder, inlineCode} from "discord.js";
import {EmbedLibrary, EmbedType} from "./EmbedLibrary";
export class PermissionError extends Error { export class PermissionError extends Error {
shouldLog: boolean = false; shouldLog: boolean = false;
@ -10,15 +11,22 @@ export class PermissionError extends Error {
super(message); super(message);
} }
public getDiscordMessage(e: PermissionError): string { public getEmbed(e: PermissionError): EmbedBuilder {
let userMessage = `:x: You can not perform this action! ${inlineCode(e.message)}` const embed = EmbedLibrary.base(
if (e.tryInstead) { "You can not perform this action!",
userMessage += ` inlineCode(e.message),
EmbedType.Error
).setFooter({
text: "Type: Permission"
});
You can try the following: if (e.tryInstead) {
${inlineCode(e.tryInstead)}` embed.addFields({
name: "You can try the following:",
value: e.tryInstead
})
} }
return userMessage; return embed;
} }
} }

View file

@ -1,4 +1,5 @@
import {inlineCode} from "discord.js"; import {EmbedBuilder, inlineCode} from "discord.js";
import {EmbedLibrary, EmbedType} from "./EmbedLibrary";
export class UserError extends Error { export class UserError extends Error {
@ -12,15 +13,22 @@ export class UserError extends Error {
} }
public getDiscordMessage(e: UserError): string { public getEmbed(e: UserError): EmbedBuilder {
let userMessage = `:x: \`${e.message}\` - Please validate your request!` const embed = EmbedLibrary.base(
if (e.tryInstead) { "Please validate your request!",
userMessage += ` inlineCode(e.message),
EmbedType.Error
).setFooter({
text: "Type: Request"
});
You can try the following: if (e.tryInstead) {
${inlineCode(e.tryInstead)}` embed.addFields({
name: "You can try the following:",
value: e.tryInstead
})
} }
return userMessage; return embed;
} }
} }

View file

@ -18,12 +18,12 @@ import {ComponentInteractionEvent} from "../Events/EventClasses/ComponentInterac
import {MenuTraversal} from "./MenuTraversal"; import {MenuTraversal} from "./MenuTraversal";
import {Prompt} from "./Modals/Prompt"; import {Prompt} from "./Modals/Prompt";
import _ from "lodash"; import _ from "lodash";
import {EmbedLibrary} from "../Discord/EmbedLibrary";
export class MenuRenderer { export class MenuRenderer {
private readonly menuId: string; private readonly menuId: string;
private eventId: Nullable<string>; private eventId: Nullable<string>;
private exitButton: ButtonBuilder;
private backButton: ButtonBuilder; private backButton: ButtonBuilder;
private static MAX_BUTTON_PER_ROW = 5; private static MAX_BUTTON_PER_ROW = 5;
private static MAX_ROWS = 5; private static MAX_ROWS = 5;
@ -33,18 +33,13 @@ export class MenuRenderer {
constructor( constructor(
private readonly traversal: MenuTraversal, private readonly traversal: MenuTraversal,
private readonly eventHandler: EventHandler|null = null, private readonly eventHandler: EventHandler|null = null,
private readonly iconCache: Nullable<IconCache> = null private readonly iconCache: Nullable<IconCache> = null,
private readonly rootName: string = '',
) { ) {
this.eventHandler ??= Container.get<EventHandler>(EventHandler.name); this.eventHandler ??= Container.get<EventHandler>(EventHandler.name);
this.iconCache ??= Container.get<IconCache>(IconCache.name); this.iconCache ??= Container.get<IconCache>(IconCache.name);
this.menuId = randomUUID(); this.menuId = randomUUID();
this.exitButton = new ButtonBuilder()
.setLabel("Exit")
.setStyle(ButtonStyle.Danger)
.setCustomId(this.getInteractionId("EXIT"))
.setEmoji(this.iconCache?.get("door_open_solid_white") ?? '');
this.backButton = new ButtonBuilder() this.backButton = new ButtonBuilder()
.setLabel("Back") .setLabel("Back")
.setStyle(ButtonStyle.Secondary) .setStyle(ButtonStyle.Secondary)
@ -119,11 +114,11 @@ export class MenuRenderer {
} }
private getEmbed(): EmbedBuilder { private getEmbed(): EmbedBuilder {
const embed = new EmbedBuilder() const embed = EmbedLibrary.base(
.setTitle(this.traversal.currentMenuItem.label) this.traversal.currentMenuItem.label,
.setDescription(this.traversal.currentMenuItem.description ?? '') this.traversal.currentMenuItem.description ?? '',
.setAuthor({ ).setFooter({
name: "/ " + this.traversal.path.join(' / ') text: this.rootName + " / " + this.traversal.getTraversedLabels().join(' / ')
}); });
if (this.traversal.currentMenuItem.type === MenuItemType.Field) { if (this.traversal.currentMenuItem.type === MenuItemType.Field) {

View file

@ -62,6 +62,16 @@ export class MenuTraversal {
return <AnyMenuItem>this.traversalMap.get(path); return <AnyMenuItem>this.traversalMap.get(path);
} }
public getTraversedLabels(): string[] {
const labels = [];
for (let i = 0; i < this.currentPath.length; i++) {
const path = this.currentPath.slice(0, i + 1);
labels.push(this.getMenuItem(this.stringifyTraversalPath(path)).label);
}
return labels;
}
public static unstringifyTraversalPath(path: StringifiedTraversalPath): TraversalPath { public static unstringifyTraversalPath(path: StringifiedTraversalPath): TraversalPath {
return path.split('/'); return path.split('/');
} }