refactor(menu): Made sure the menu can be used for more than group
This commit is contained in:
parent
a79898b2e9
commit
1d73ee8a78
16 changed files with 650 additions and 406 deletions
|
|
@ -0,0 +1,162 @@
|
|||
import {EventHandler} from "../Events/EventHandler";
|
||||
import {Container} from "../Container/Container";
|
||||
import {AnyMenuItem, MenuAction, MenuItemType, TraversalPath} from "./MenuRenderer.types";
|
||||
import {randomUUID} from "node:crypto";
|
||||
import {ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, MessageFlags} from "discord.js";
|
||||
import {Nullable} from "../types/Nullable";
|
||||
import {IconCache} from "../Icons/IconCache";
|
||||
import {MessageActionRowComponentBuilder} from "@discordjs/builders";
|
||||
import {ComponentInteractionEvent} from "../Events/ComponentInteractionEvent";
|
||||
import {MenuTraversal} from "./MenuTraversal";
|
||||
|
||||
export class MenuRenderer {
|
||||
private readonly menuId: string;
|
||||
private eventId: Nullable<string>;
|
||||
|
||||
private exitButton: ButtonBuilder;
|
||||
private backButton: ButtonBuilder;
|
||||
|
||||
constructor(
|
||||
private readonly traversal: MenuTraversal,
|
||||
private readonly eventHandler: EventHandler|null = null,
|
||||
private readonly iconCache: Nullable<IconCache> = null
|
||||
) {
|
||||
this.eventHandler ??= Container.get<EventHandler>(EventHandler.name);
|
||||
this.iconCache ??= Container.get<IconCache>(IconCache.name);
|
||||
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()
|
||||
.setLabel("Back")
|
||||
.setStyle(ButtonStyle.Secondary)
|
||||
.setCustomId(this.getInteractionId("MOVE", "BACK"))
|
||||
.setEmoji(this.iconCache?.get("angle_left_solid") ?? '');
|
||||
}
|
||||
|
||||
public async display(interaction: CommandInteraction) {
|
||||
this.eventId = this.eventHandler?.addHandler<ComponentInteractionEvent>(ComponentInteractionEvent.name, this.handleUIEvents.bind(this));
|
||||
|
||||
await interaction.reply({
|
||||
content: "",
|
||||
components: this.getActionRows(),
|
||||
embeds: [this.getEmbed()],
|
||||
withResponse: true,
|
||||
flags: MessageFlags.Ephemeral
|
||||
})
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.eventHandler?.removeHandler(ComponentInteractionEvent.name, this.eventId ?? '');
|
||||
}
|
||||
|
||||
private getActionRows(): ActionRowBuilder<MessageActionRowComponentBuilder>[] {
|
||||
const navigation = new ActionRowBuilder<ButtonBuilder>()
|
||||
|
||||
if (!this.traversal.isRoot) {
|
||||
navigation.addComponents(this.backButton);
|
||||
}
|
||||
|
||||
const rows = [
|
||||
this.getComponentForMenuItem(this.traversal.currentMenuItem),
|
||||
navigation
|
||||
];
|
||||
|
||||
return rows.filter(row => row.components.length > 0);
|
||||
}
|
||||
|
||||
private getComponentForMenuItem(menuItem: AnyMenuItem): ActionRowBuilder<MessageActionRowComponentBuilder> {
|
||||
if (menuItem.type === MenuItemType.Collection) {
|
||||
const navigation = new ActionRowBuilder<ButtonBuilder>();
|
||||
navigation.setComponents(
|
||||
...menuItem.children.map(item => new ButtonBuilder()
|
||||
.setLabel(item.label)
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setCustomId(this.getInteractionId("MOVE", item.traversalKey))
|
||||
.setEmoji(this.iconCache?.get(item.type === MenuItemType.Field ? 'pen_solid' : "folder_solid") ?? '')
|
||||
)
|
||||
)
|
||||
|
||||
return navigation
|
||||
}
|
||||
|
||||
if (menuItem.type === MenuItemType.Field) {
|
||||
const action = menuItem.getActionRowBuilder({
|
||||
path: this.traversal.path
|
||||
})
|
||||
action.setCustomId(this.getInteractionId("SET", this.traversal.stringifiedPath));
|
||||
return new ActionRowBuilder<MessageActionRowComponentBuilder>().setComponents([action]);
|
||||
}
|
||||
|
||||
return new ActionRowBuilder();
|
||||
}
|
||||
|
||||
private getEmbed(): EmbedBuilder {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(this.traversal.currentMenuItem.label)
|
||||
.setDescription(this.traversal.currentMenuItem.description ?? '')
|
||||
.setAuthor({
|
||||
name: "/ " + this.traversal.path.join(' / ')
|
||||
});
|
||||
|
||||
if (this.traversal.currentMenuItem.type === MenuItemType.Field) {
|
||||
const currentValue = this.traversal.currentMenuItem.getCurrentValue({
|
||||
path: this.traversal.path
|
||||
});
|
||||
embed.addFields({
|
||||
name: "Current Value",
|
||||
value: currentValue
|
||||
});
|
||||
}
|
||||
|
||||
return embed;
|
||||
}
|
||||
|
||||
private getInteractionId(action: MenuAction, parameter: Nullable<string> = undefined): string {
|
||||
return `${this.menuId};${action};${parameter ?? ''}`
|
||||
}
|
||||
|
||||
private handleUIEvents(ev: ComponentInteractionEvent) {
|
||||
if (!ev.interaction.customId.startsWith(this.menuId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [, action, parameter ] = ev.interaction.customId.split(';')
|
||||
const menuAction = <MenuAction>action;
|
||||
|
||||
switch (menuAction) {
|
||||
case "MOVE":
|
||||
if (parameter === 'BACK') {
|
||||
this.traversal.travelBack();
|
||||
break;
|
||||
}
|
||||
|
||||
this.traversal.travelForward(parameter);
|
||||
break;
|
||||
case "SET":
|
||||
{
|
||||
const value = ev.interaction.isAnySelectMenu() ? ev.interaction.values : [""];
|
||||
const menuItem = this.traversal.getMenuItem(parameter);
|
||||
|
||||
if (menuItem.type !== MenuItemType.Field) {
|
||||
break;
|
||||
}
|
||||
|
||||
menuItem.setValue(value, {
|
||||
path: MenuTraversal.unstringifyTraversalPath(parameter)
|
||||
})
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ev.interaction.update({
|
||||
components: this.getActionRows(),
|
||||
embeds: [ this.getEmbed() ]
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue