defer update if there's no component update

This commit is contained in:
MapleLeaf
2021-12-27 21:41:14 -06:00
parent 3d89b4fe6f
commit 9ab1f2b689
7 changed files with 61 additions and 8 deletions

View File

@@ -84,6 +84,7 @@ function createReacordComponentInteraction(
update: async (options) => { update: async (options) => {
await interaction.update(getDiscordMessageOptions(options)) await interaction.update(getDiscordMessageOptions(options))
}, },
deferUpdate: () => interaction.deferUpdate(),
} }
} }
@@ -97,6 +98,7 @@ function createReacordComponentInteraction(
update: async (options) => { update: async (options) => {
await interaction.update(getDiscordMessageOptions(options)) await interaction.update(getDiscordMessageOptions(options))
}, },
deferUpdate: () => interaction.deferUpdate(),
} }
} }
@@ -106,13 +108,13 @@ function createReacordComponentInteraction(
// TODO: this could be a part of the core library, // TODO: this could be a part of the core library,
// and also handle some edge cases, e.g. empty messages // and also handle some edge cases, e.g. empty messages
function getDiscordMessageOptions( function getDiscordMessageOptions(
options: MessageOptions, reacordOptions: MessageOptions,
): Discord.MessageOptions { ): Discord.MessageOptions {
return { const options: Discord.MessageOptions = {
// eslint-disable-next-line unicorn/no-null // eslint-disable-next-line unicorn/no-null
content: options.content || null, content: reacordOptions.content || null,
embeds: options.embeds, embeds: reacordOptions.embeds,
components: options.actionRows.map((row) => ({ components: reacordOptions.actionRows.map((row) => ({
type: "ACTION_ROW", type: "ACTION_ROW",
components: row.map( components: row.map(
(component): Discord.MessageActionRowComponentOptions => { (component): Discord.MessageActionRowComponentOptions => {
@@ -143,6 +145,12 @@ function getDiscordMessageOptions(
), ),
})), })),
} }
if (!options.content && !options.embeds?.length) {
options.content = "_ _"
}
return options
} }
function createReacordMessage(message: Discord.Message): Message { function createReacordMessage(message: Discord.Message): Message {

View File

@@ -18,6 +18,7 @@ export type ButtonInteraction = {
channelId: string channelId: string
customId: string customId: string
update(options: MessageOptions): Promise<void> update(options: MessageOptions): Promise<void>
deferUpdate(): Promise<void>
} }
export type SelectInteraction = { export type SelectInteraction = {
@@ -27,4 +28,5 @@ export type SelectInteraction = {
customId: string customId: string
values: string[] values: string[]
update(options: MessageOptions): Promise<void> update(options: MessageOptions): Promise<void>
deferUpdate(): Promise<void>
} }

View File

@@ -4,6 +4,7 @@ import { Container } from "./container.js"
import type { ComponentInteraction } from "./interaction" import type { ComponentInteraction } from "./interaction"
import type { Message, MessageOptions } from "./message" import type { Message, MessageOptions } from "./message"
import type { Node } from "./node.js" import type { Node } from "./node.js"
import { Timeout } from "./timeout"
type UpdatePayload = type UpdatePayload =
| { action: "update" | "deactivate"; options: MessageOptions } | { action: "update" | "deactivate"; options: MessageOptions }
@@ -20,6 +21,10 @@ export abstract class Renderer {
.pipe(concatMap((payload) => this.updateMessage(payload))) .pipe(concatMap((payload) => this.updateMessage(payload)))
.subscribe({ error: console.error }) .subscribe({ error: console.error })
private deferUpdateTimeout = new Timeout(500, () => {
this.componentInteraction?.deferUpdate().catch(console.error)
})
render() { render() {
if (!this.active) { if (!this.active) {
console.warn("Attempted to update a deactivated message") console.warn("Attempted to update a deactivated message")
@@ -47,6 +52,7 @@ export abstract class Renderer {
handleComponentInteraction(interaction: ComponentInteraction) { handleComponentInteraction(interaction: ComponentInteraction) {
this.componentInteraction = interaction this.componentInteraction = interaction
this.deferUpdateTimeout.run()
for (const node of this.nodes) { for (const node of this.nodes) {
if (node.handleComponentInteraction(interaction)) { if (node.handleComponentInteraction(interaction)) {
return true return true
@@ -84,6 +90,7 @@ export abstract class Renderer {
if (this.componentInteraction) { if (this.componentInteraction) {
const promise = this.componentInteraction.update(payload.options) const promise = this.componentInteraction.update(payload.options)
this.componentInteraction = undefined this.componentInteraction = undefined
this.deferUpdateTimeout.cancel()
await promise await promise
return return
} }

View File

@@ -0,0 +1,20 @@
export class Timeout {
private timeoutId?: NodeJS.Timeout
constructor(
private readonly time: number,
private readonly callback: () => void,
) {}
run() {
this.cancel()
this.timeoutId = setTimeout(this.callback, this.time)
}
cancel() {
if (this.timeoutId) {
clearTimeout(this.timeoutId)
this.timeoutId = undefined
}
}
}

View File

@@ -1,7 +1,7 @@
import { Client } from "discord.js" import { Client } from "discord.js"
import "dotenv/config" import "dotenv/config"
import React from "react" import React from "react"
import { ReacordDiscordJs } from "../library/main" import { Button, ReacordDiscordJs } from "../library/main"
import { createCommandHandler } from "./command-handler" import { createCommandHandler } from "./command-handler"
import { Counter } from "./counter" import { Counter } from "./counter"
import { FruitSelect } from "./fruit-select" import { FruitSelect } from "./fruit-select"
@@ -36,6 +36,16 @@ client.on("ready", () => {
}) })
createCommandHandler(client, [ createCommandHandler(client, [
{
name: "button",
description: "it's a button",
run: (interaction) => {
reacord.reply(
interaction,
<Button label="clic" onClick={() => console.log("was clic")} />,
)
},
},
{ {
name: "counter", name: "counter",
description: "shows a counter button", description: "shows a counter button",

6
test/renderer.test.ts Normal file
View File

@@ -0,0 +1,6 @@
// test that the interaction update is _eventually_ deferred if there's no component update,
// and that update isn't called after the fact
// ...somehow
test.todo("defer update timeout")
export {}

View File

@@ -28,7 +28,7 @@
# internal # internal
- [ ] combine `MessageOptions` and `Message` into a single message object (?) - [ ] combine `MessageOptions` and `Message` into a single message object (?)
- [ ] consider always calling `deferUpdate` on component interactions - [x] consider always calling `deferUpdate` on component interactions
# cool ideas / polish # cool ideas / polish
@@ -45,4 +45,4 @@
- [ ] uncontrolled select - [ ] uncontrolled select
- [x] single class/helper function for testing `ReacordTester` - [x] single class/helper function for testing `ReacordTester`
- [ ] handle deletion outside of reacord - [ ] handle deletion outside of reacord
- [ ] for more easily writing adapters, address discord API nuances at the reacord level instead of the adapter level. the goal being that adapters can just take the objects and send them to discord - [ ] for more easily writing adapters, address discord API nuances at the reacord level instead of the adapter level. the goal being that adapters can just take the objects and send them to discord. probably make use of discord api types for this