pnp-scheduler/source/Menu/MenuRenderer.ts

162 lines
No EOL
5.3 KiB
TypeScript

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() ]
})
}
}