diff --git a/package.json b/package.json index 8110b8c..30834cb 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@discordjs/rest": "^0.2.0-canary.0", "@hokify/agenda": "^6.2.12", "@tsconfig/node18-strictest-esm": "^1.0.0", + "@types/lodash": "^4.14.191", "@types/node-cron": "^3.0.1", "@ungap/structured-clone": "^1.0.1", "agenda": "^4.3.0", @@ -17,6 +18,7 @@ "fuse.js": "^6.6.2", "humanize-duration": "^3.27.1", "immutable": "^4.1.0", + "lodash": "^4.17.21", "mongodb": "^4.7.0", "node-cron": "^3.0.0", "node-fetch": "^3.3.0", @@ -24,7 +26,7 @@ "pastebin-api": "^5.1.1", "structured-clone": "^0.2.2", "systeminformation": "^5.17.3", - "typescript": "^5.0.0-dev.20230102", + "typescript": "^4.9.4", "uuid": "^8.3.2" }, "name": "nucleus", diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 2cdd1f8..1591273 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -7,7 +7,9 @@ import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import createPageIndicator from "../../utils/createPageIndicator.js"; import { configToDropdown } from "../../actions/roleMenu.js"; - +import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import lodash from 'lodash'; +const isEqual = lodash.isEqual; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("rolemenu") @@ -25,7 +27,17 @@ interface ObjectSchema { }[]; } -const editNameDescription = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => { +const defaultRolePageConfig = { + name: "Role Menu Page", + description: "A new role menu page", + min: 0, + max: 0, + options: [ + {name: "Role 1", description: null, role: "No role set"} + ] +} + +const editNameDescription = async (i: ButtonInteraction, interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data: {name?: string, description?: string}) => { let {name, description} = data; const modal = new ModalBuilder() @@ -35,14 +47,21 @@ const editNameDescription = async (interaction: StringSelectMenuInteraction | Bu new ActionRowBuilder() .addComponents( new TextInputBuilder() + .setLabel("Name") .setCustomId("name") - .setPlaceholder(name ?? "") + .setPlaceholder("Name here...") // TODO: Make better placeholder .setStyle(TextInputStyle.Short) - .setRequired(true), + .setValue(name ?? "") + .setRequired(true) + ), + new ActionRowBuilder() + .addComponents( new TextInputBuilder() + .setLabel("Description") .setCustomId("description") - .setPlaceholder(description ?? "") + .setPlaceholder("Description here...") // TODO: Make better placeholder .setStyle(TextInputStyle.Short) + .setValue(description ?? "") ) ) const button = new ActionRowBuilder() @@ -54,6 +73,33 @@ const editNameDescription = async (interaction: StringSelectMenuInteraction | Bu .setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji) ) + await i.showModal(modal) + await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setTitle("Role Menu") + .setDescription("Modal opened. If you can't see it, click back and try again.") + .setStatus("Success") + ], + components: [button] + }); + + let out: Discord.ModalSubmitInteraction | null; + try { + out = await modalInteractionCollector( + m, + (m) => m.channel!.id === interaction.channel!.id, + (_) => true + ) as Discord.ModalSubmitInteraction | null; + } catch (e) { + console.error(e); + out = null; + } + if(!out) return [name, description]; + if (out.isButton()) return [name, description]; + if(!out.fields) return [name, description]; + name = out.fields.fields.find((f) => f.customId === "name")?.value ?? name; + description = out.fields.fields.find((f) => f.customId === "description")?.value ?? description; return [name, description] } @@ -63,7 +109,7 @@ const ellipsis = (str: string, max: number): string => { return str.slice(0, max - 3) + "..."; } -const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema) => { +const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | ButtonInteraction, m: Message, data?: ObjectSchema): Promise => { if (!data) data = { name: "Role Menu Page", description: "A new role menu page", @@ -91,6 +137,11 @@ const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | But ); let back = false + if(data.options.length === 0) { + data.options = [ + {name: "Role 1", description: null, role: "No role set"} + ] + } do { const previewSelect = configToDropdown("Edit Roles", {name: data.name, description: data.description, min: 1, max: 1, options: data.options}); const embed = new EmojiEmbed() @@ -117,23 +168,25 @@ const createRoleMenuPage = async (interaction: StringSelectMenuInteraction | But await createRoleMenuOptionPage(interaction, m, data.options.find((o) => o.role === (i as StringSelectMenuInteraction).values[0])); } } else if (i.isButton()) { - await i.deferUpdate(); switch (i.customId) { case "back": + await i.deferUpdate(); back = true; break; case "edit": - let [name, description] = await editNameDescription(interaction, m, data); + let [name, description] = await editNameDescription(i, interaction, m, data); data.name = name ? name : data.name; data.description = description ? description : data.description; break; case "addRole": + await i.deferUpdate(); data.options.push(await createRoleMenuOptionPage(interaction, m)); break; } } } while (!back); + if(isEqual(data, defaultRolePageConfig)) return null; return data; } @@ -184,14 +237,14 @@ const createRoleMenuOptionPage = async (interaction: StringSelectMenuInteraction data.role = (i as RoleSelectMenuInteraction).values[0]!; } } else if (i.isButton()) { - await i.deferUpdate(); switch (i.customId) { case "back": + await i.deferUpdate(); back = true; break; case "edit": await i.deferUpdate(); - let [name, description] = await editNameDescription(interaction, m, data as {name: string; description: string}); + let [name, description] = await editNameDescription(i, interaction, m, data as {name: string; description: string}); data.name = name ? name : data.name; data.description = description ? description : data.description; break; @@ -315,7 +368,9 @@ const callback = async (interaction: CommandInteraction): Promise => { page++; break; case "add": - currentObject.push(await createRoleMenuPage(i, m)); + let newPage = await createRoleMenuPage(i, m) + if(!newPage) break; + currentObject.push(); page = currentObject.length - 1; break; case "reorder": @@ -330,10 +385,14 @@ const callback = async (interaction: CommandInteraction): Promise => { case "action": switch(i.values[0]) { case "edit": - currentObject[page] = await createRoleMenuPage(i, m, current!); + let edited = await createRoleMenuPage(i, m, current!); + if(!edited) break; + currentObject[page] = edited; modified = true; break; case "delete": + if(page === 0 && currentObject.keys.length - 1 > 0) page++; + else page--; currentObject.splice(page, 1); break; } diff --git a/src/commands/settings/stats.ts b/src/commands/settings/stats.ts index f8c7d1e..dd4027d 100644 --- a/src/commands/settings/stats.ts +++ b/src/commands/settings/stats.ts @@ -1,5 +1,5 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction } from "discord.js"; +import Discord, { CommandInteraction, Message, ActionRowBuilder, StringSelectMenuBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, APIMessageComponentEmoji, TextInputBuilder, StringSelectMenuInteraction, ButtonInteraction, MessageComponentInteraction, ChannelSelectMenuBuilder, ChannelSelectMenuInteraction, ModalBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; @@ -18,7 +18,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => const showModal = async (interaction: MessageComponentInteraction, current: { enabled: boolean; name: string; }) => { await interaction.showModal( - new Discord.ModalBuilder() + new ModalBuilder() .setCustomId("modal") .setTitle(`Stats channel name`) .addComponents(