component events
This commit is contained in:
8
library/core/component-event.ts
Normal file
8
library/core/component-event.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { ReactNode } from "react"
|
||||
import type { ReacordInstance } from "./instance"
|
||||
|
||||
export type ComponentEvent = {
|
||||
// todo: add more info, like user, channel, member, guild, etc.
|
||||
reply(content?: ReactNode): ReacordInstance
|
||||
ephemeralReply(content?: ReactNode): ReacordInstance
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import type { ComponentInteraction } from "../../internal/interaction"
|
||||
import type { MessageOptions } from "../../internal/message"
|
||||
import { getNextActionRow } from "../../internal/message"
|
||||
import { Node } from "../../internal/node.js"
|
||||
import type { ComponentEvent } from "../component-event"
|
||||
|
||||
export type ButtonProps = {
|
||||
label?: string
|
||||
@@ -14,7 +15,7 @@ export type ButtonProps = {
|
||||
onClick: (event: ButtonClickEvent) => void
|
||||
}
|
||||
|
||||
export type ButtonClickEvent = {}
|
||||
export type ButtonClickEvent = ComponentEvent
|
||||
|
||||
export function Button(props: ButtonProps) {
|
||||
return (
|
||||
@@ -41,7 +42,7 @@ class ButtonNode extends Node<ButtonProps> {
|
||||
interaction.type === "button" &&
|
||||
interaction.customId === this.customId
|
||||
) {
|
||||
this.props.onClick(interaction)
|
||||
this.props.onClick(interaction.event)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ReacordElement } from "../../internal/element.js"
|
||||
import type { ComponentInteraction } from "../../internal/interaction"
|
||||
import type { ActionRow, MessageOptions } from "../../internal/message"
|
||||
import { Node } from "../../internal/node.js"
|
||||
import type { ComponentEvent } from "../component-event"
|
||||
import { OptionNode } from "./option-node"
|
||||
|
||||
export type SelectProps = {
|
||||
@@ -17,12 +18,12 @@ export type SelectProps = {
|
||||
minValues?: number
|
||||
maxValues?: number
|
||||
disabled?: boolean
|
||||
onSelect?: (event: SelectEvent) => void
|
||||
onSelectValue?: (value: string) => void
|
||||
onSelectMultiple?: (values: string[]) => void
|
||||
onChange?: (event: SelectChangeEvent) => void
|
||||
onChangeValue?: (value: string, event: SelectChangeEvent) => void
|
||||
onChangeMultiple?: (values: string[], event: SelectChangeEvent) => void
|
||||
}
|
||||
|
||||
export type SelectEvent = {
|
||||
export type SelectChangeEvent = ComponentEvent & {
|
||||
values: string[]
|
||||
}
|
||||
|
||||
@@ -52,9 +53,9 @@ class SelectNode extends Node<SelectProps> {
|
||||
minValues = 0,
|
||||
maxValues = 25,
|
||||
children,
|
||||
onSelect,
|
||||
onSelectValue,
|
||||
onSelectMultiple,
|
||||
onChange,
|
||||
onChangeValue,
|
||||
onChangeMultiple,
|
||||
...props
|
||||
} = this.props
|
||||
|
||||
@@ -72,18 +73,18 @@ class SelectNode extends Node<SelectProps> {
|
||||
override handleComponentInteraction(
|
||||
interaction: ComponentInteraction,
|
||||
): boolean {
|
||||
if (
|
||||
const isSelectInteraction =
|
||||
interaction.type === "select" &&
|
||||
interaction.customId === this.customId &&
|
||||
!this.props.disabled
|
||||
) {
|
||||
this.props.onSelect?.({ values: interaction.values })
|
||||
this.props.onSelectMultiple?.(interaction.values)
|
||||
if (interaction.values[0]) {
|
||||
this.props.onSelectValue?.(interaction.values[0])
|
||||
}
|
||||
return true
|
||||
|
||||
if (!isSelectInteraction) return false
|
||||
|
||||
this.props.onChange?.(interaction.event)
|
||||
this.props.onChangeMultiple?.(interaction.event.values, interaction.event)
|
||||
if (interaction.event.values[0]) {
|
||||
this.props.onChangeValue?.(interaction.event.values[0], interaction.event)
|
||||
}
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
7
library/core/instance.ts
Normal file
7
library/core/instance.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
export type ReacordInstance = {
|
||||
render: (content: ReactNode) => void
|
||||
deactivate: () => void
|
||||
destroy: () => void
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
/* eslint-disable class-methods-use-this */
|
||||
import type * as Discord from "discord.js"
|
||||
import type { ReactNode } from "react"
|
||||
import type { Except } from "type-fest"
|
||||
import { raise } from "../../helpers/raise"
|
||||
import { toUpper } from "../../helpers/to-upper"
|
||||
import type { ComponentInteraction } from "../internal/interaction"
|
||||
import type { Message, MessageOptions } from "../internal/message"
|
||||
import { ChannelMessageRenderer } from "../internal/renderers/channel-message-renderer"
|
||||
import { CommandReplyRenderer } from "../internal/renderers/command-reply-renderer"
|
||||
import type { ReacordConfig, ReacordInstance } from "./reacord"
|
||||
import { InteractionReplyRenderer } from "../internal/renderers/interaction-reply-renderer"
|
||||
import type { ReacordInstance } from "./instance"
|
||||
import type { ReacordConfig } from "./reacord"
|
||||
import { Reacord } from "./reacord"
|
||||
|
||||
export class ReacordDiscordJs extends Reacord {
|
||||
@@ -15,7 +19,7 @@ export class ReacordDiscordJs extends Reacord {
|
||||
client.on("interactionCreate", (interaction) => {
|
||||
if (interaction.isMessageComponent()) {
|
||||
this.handleComponentInteraction(
|
||||
createReacordComponentInteraction(interaction),
|
||||
this.createReacordComponentInteraction(interaction),
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -26,21 +30,7 @@ export class ReacordDiscordJs extends Reacord {
|
||||
initialContent?: React.ReactNode,
|
||||
): ReacordInstance {
|
||||
return this.createInstance(
|
||||
new ChannelMessageRenderer({
|
||||
send: async (options) => {
|
||||
const channel =
|
||||
this.client.channels.cache.get(channelId) ??
|
||||
(await this.client.channels.fetch(channelId)) ??
|
||||
raise(`Channel ${channelId} not found`)
|
||||
|
||||
if (!channel.isText()) {
|
||||
raise(`Channel ${channelId} is not a text channel`)
|
||||
}
|
||||
|
||||
const message = await channel.send(getDiscordMessageOptions(options))
|
||||
return createReacordMessage(message)
|
||||
},
|
||||
}),
|
||||
this.createChannelRenderer(channelId),
|
||||
initialContent,
|
||||
)
|
||||
}
|
||||
@@ -50,25 +40,7 @@ export class ReacordDiscordJs extends Reacord {
|
||||
initialContent?: React.ReactNode,
|
||||
): ReacordInstance {
|
||||
return this.createInstance(
|
||||
new CommandReplyRenderer({
|
||||
type: "command",
|
||||
id: interaction.id,
|
||||
channelId: interaction.channelId,
|
||||
reply: async (options) => {
|
||||
const message = await interaction.reply({
|
||||
...getDiscordMessageOptions(options),
|
||||
fetchReply: true,
|
||||
})
|
||||
return createReacordMessage(message as Discord.Message)
|
||||
},
|
||||
followUp: async (options) => {
|
||||
const message = await interaction.followUp({
|
||||
...getDiscordMessageOptions(options),
|
||||
fetchReply: true,
|
||||
})
|
||||
return createReacordMessage(message as Discord.Message)
|
||||
},
|
||||
}),
|
||||
this.createInteractionReplyRenderer(interaction),
|
||||
initialContent,
|
||||
)
|
||||
}
|
||||
@@ -78,61 +50,180 @@ export class ReacordDiscordJs extends Reacord {
|
||||
initialContent?: React.ReactNode,
|
||||
): ReacordInstance {
|
||||
return this.createInstance(
|
||||
new CommandReplyRenderer({
|
||||
type: "command",
|
||||
id: interaction.id,
|
||||
channelId: interaction.channelId,
|
||||
reply: async (options) => {
|
||||
await interaction.reply({
|
||||
...getDiscordMessageOptions(options),
|
||||
ephemeral: true,
|
||||
})
|
||||
return createEphemeralReacordMessage()
|
||||
},
|
||||
followUp: async (options) => {
|
||||
await interaction.followUp({
|
||||
...getDiscordMessageOptions(options),
|
||||
ephemeral: true,
|
||||
})
|
||||
return createEphemeralReacordMessage()
|
||||
},
|
||||
}),
|
||||
this.createEphemeralInteractionReplyRenderer(interaction),
|
||||
initialContent,
|
||||
)
|
||||
}
|
||||
|
||||
private createChannelRenderer(channelId: string) {
|
||||
return new ChannelMessageRenderer({
|
||||
send: async (options) => {
|
||||
const channel =
|
||||
this.client.channels.cache.get(channelId) ??
|
||||
(await this.client.channels.fetch(channelId)) ??
|
||||
raise(`Channel ${channelId} not found`)
|
||||
|
||||
if (!channel.isText()) {
|
||||
raise(`Channel ${channelId} is not a text channel`)
|
||||
}
|
||||
|
||||
const message = await channel.send(getDiscordMessageOptions(options))
|
||||
return createReacordMessage(message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private createInteractionReplyRenderer(
|
||||
interaction:
|
||||
| Discord.CommandInteraction
|
||||
| Discord.MessageComponentInteraction,
|
||||
) {
|
||||
return new InteractionReplyRenderer({
|
||||
type: "command",
|
||||
id: interaction.id,
|
||||
reply: async (options) => {
|
||||
const message = await interaction.reply({
|
||||
...getDiscordMessageOptions(options),
|
||||
fetchReply: true,
|
||||
})
|
||||
return createReacordMessage(message as Discord.Message)
|
||||
},
|
||||
followUp: async (options) => {
|
||||
const message = await interaction.followUp({
|
||||
...getDiscordMessageOptions(options),
|
||||
fetchReply: true,
|
||||
})
|
||||
return createReacordMessage(message as Discord.Message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private createEphemeralInteractionReplyRenderer(
|
||||
interaction:
|
||||
| Discord.CommandInteraction
|
||||
| Discord.MessageComponentInteraction,
|
||||
) {
|
||||
return new InteractionReplyRenderer({
|
||||
type: "command",
|
||||
id: interaction.id,
|
||||
reply: async (options) => {
|
||||
await interaction.reply({
|
||||
...getDiscordMessageOptions(options),
|
||||
ephemeral: true,
|
||||
})
|
||||
return createEphemeralReacordMessage()
|
||||
},
|
||||
followUp: async (options) => {
|
||||
await interaction.followUp({
|
||||
...getDiscordMessageOptions(options),
|
||||
ephemeral: true,
|
||||
})
|
||||
return createEphemeralReacordMessage()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private createReacordComponentInteraction(
|
||||
interaction: Discord.MessageComponentInteraction,
|
||||
): ComponentInteraction {
|
||||
const baseProps: Except<ComponentInteraction, "type"> = {
|
||||
id: interaction.id,
|
||||
customId: interaction.customId,
|
||||
update: async (options: MessageOptions) => {
|
||||
await interaction.update(getDiscordMessageOptions(options))
|
||||
},
|
||||
deferUpdate: async () => {
|
||||
if (interaction.replied || interaction.deferred) return
|
||||
await interaction.deferUpdate()
|
||||
},
|
||||
reply: async (options) => {
|
||||
const message = await interaction.reply({
|
||||
...getDiscordMessageOptions(options),
|
||||
fetchReply: true,
|
||||
})
|
||||
return createReacordMessage(message as Discord.Message)
|
||||
},
|
||||
followUp: async (options) => {
|
||||
const message = await interaction.followUp({
|
||||
...getDiscordMessageOptions(options),
|
||||
fetchReply: true,
|
||||
})
|
||||
return createReacordMessage(message as Discord.Message)
|
||||
},
|
||||
event: {
|
||||
reply: (content?: ReactNode) =>
|
||||
this.createInstance(
|
||||
this.createInteractionReplyRenderer(interaction),
|
||||
content,
|
||||
),
|
||||
|
||||
ephemeralReply: (content: ReactNode) =>
|
||||
this.createInstance(
|
||||
this.createEphemeralInteractionReplyRenderer(interaction),
|
||||
content,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
if (interaction.isButton()) {
|
||||
return {
|
||||
...baseProps,
|
||||
type: "button",
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.isSelectMenu()) {
|
||||
return {
|
||||
...baseProps,
|
||||
type: "select",
|
||||
event: {
|
||||
...baseProps.event,
|
||||
values: interaction.values,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
raise(`Unsupported component interaction type: ${interaction.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
function createReacordComponentInteraction(
|
||||
interaction: Discord.MessageComponentInteraction,
|
||||
): ComponentInteraction {
|
||||
if (interaction.isButton()) {
|
||||
return {
|
||||
type: "button",
|
||||
id: interaction.id,
|
||||
channelId: interaction.channelId,
|
||||
customId: interaction.customId,
|
||||
update: async (options) => {
|
||||
await interaction.update(getDiscordMessageOptions(options))
|
||||
},
|
||||
deferUpdate: () => interaction.deferUpdate(),
|
||||
}
|
||||
}
|
||||
function createReacordMessage(message: Discord.Message): Message {
|
||||
return {
|
||||
edit: async (options) => {
|
||||
await message.edit(getDiscordMessageOptions(options))
|
||||
},
|
||||
disableComponents: async () => {
|
||||
for (const actionRow of message.components) {
|
||||
for (const component of actionRow.components) {
|
||||
component.setDisabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.isSelectMenu()) {
|
||||
return {
|
||||
type: "select",
|
||||
id: interaction.id,
|
||||
channelId: interaction.channelId,
|
||||
customId: interaction.customId,
|
||||
values: interaction.values,
|
||||
update: async (options) => {
|
||||
await interaction.update(getDiscordMessageOptions(options))
|
||||
},
|
||||
deferUpdate: () => interaction.deferUpdate(),
|
||||
}
|
||||
await message.edit({
|
||||
components: message.components,
|
||||
})
|
||||
},
|
||||
delete: async () => {
|
||||
await message.delete()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
raise(`Unsupported component interaction type: ${interaction.type}`)
|
||||
function createEphemeralReacordMessage(): Message {
|
||||
return {
|
||||
edit: () => {
|
||||
console.warn("Ephemeral messages can't be edited")
|
||||
return Promise.resolve()
|
||||
},
|
||||
disableComponents: () => {
|
||||
console.warn("Ephemeral messages can't be edited")
|
||||
return Promise.resolve()
|
||||
},
|
||||
delete: () => {
|
||||
console.warn("Ephemeral messages can't be deleted")
|
||||
return Promise.resolve()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this could be a part of the core library,
|
||||
@@ -182,42 +273,3 @@ function getDiscordMessageOptions(
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
function createReacordMessage(message: Discord.Message): Message {
|
||||
return {
|
||||
edit: async (options) => {
|
||||
await message.edit(getDiscordMessageOptions(options))
|
||||
},
|
||||
disableComponents: async () => {
|
||||
for (const actionRow of message.components) {
|
||||
for (const component of actionRow.components) {
|
||||
component.setDisabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
await message.edit({
|
||||
components: message.components,
|
||||
})
|
||||
},
|
||||
delete: async () => {
|
||||
await message.delete()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function createEphemeralReacordMessage(): Message {
|
||||
return {
|
||||
edit: () => {
|
||||
console.warn("Ephemeral messages can't be edited")
|
||||
return Promise.resolve()
|
||||
},
|
||||
disableComponents: () => {
|
||||
console.warn("Ephemeral messages can't be edited")
|
||||
return Promise.resolve()
|
||||
},
|
||||
delete: () => {
|
||||
console.warn("Ephemeral messages can't be deleted")
|
||||
return Promise.resolve()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ import type {
|
||||
MessageSelectOptions,
|
||||
} from "../internal/message"
|
||||
import { ChannelMessageRenderer } from "../internal/renderers/channel-message-renderer"
|
||||
import { CommandReplyRenderer } from "../internal/renderers/command-reply-renderer"
|
||||
import type { ReacordInstance } from "./reacord"
|
||||
import { InteractionReplyRenderer } from "../internal/renderers/interaction-reply-renderer"
|
||||
import type { ButtonClickEvent } from "./components/button"
|
||||
import type { SelectChangeEvent } from "./components/select"
|
||||
import type { ReacordInstance } from "./instance"
|
||||
import { Reacord } from "./reacord"
|
||||
|
||||
const nextTickPromise = promisify(nextTick)
|
||||
@@ -46,7 +48,7 @@ export class ReacordTester extends Reacord {
|
||||
|
||||
override reply(): ReacordInstance {
|
||||
return this.createInstance(
|
||||
new CommandReplyRenderer(
|
||||
new InteractionReplyRenderer(
|
||||
new TestCommandInteraction(this.messageContainer),
|
||||
),
|
||||
)
|
||||
@@ -111,6 +113,10 @@ export class ReacordTester extends Reacord {
|
||||
raise(`Couldn't find select with placeholder "${placeholder}"`)
|
||||
}
|
||||
|
||||
createMessage(options: MessageOptions) {
|
||||
return new TestMessage(options, this.messageContainer)
|
||||
}
|
||||
|
||||
private createButtonActions(
|
||||
button: MessageButtonOptions,
|
||||
message: TestMessage,
|
||||
@@ -118,7 +124,7 @@ export class ReacordTester extends Reacord {
|
||||
return {
|
||||
click: () => {
|
||||
this.handleComponentInteraction(
|
||||
new TestButtonInteraction(button.customId, message),
|
||||
new TestButtonInteraction(button.customId, message, this),
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -131,7 +137,7 @@ export class ReacordTester extends Reacord {
|
||||
return {
|
||||
select: (...values: string[]) => {
|
||||
this.handleComponentInteraction(
|
||||
new TestSelectInteraction(component.customId, message, values),
|
||||
new TestSelectInteraction(component.customId, message, values, this),
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -189,13 +195,25 @@ class TestInteraction {
|
||||
readonly id = nanoid()
|
||||
readonly channelId = "test-channel-id"
|
||||
|
||||
constructor(readonly customId: string, readonly message: TestMessage) {}
|
||||
constructor(
|
||||
readonly customId: string,
|
||||
readonly message: TestMessage,
|
||||
private tester: ReacordTester,
|
||||
) {}
|
||||
|
||||
async update(options: MessageOptions): Promise<void> {
|
||||
this.message.options = options
|
||||
}
|
||||
|
||||
async deferUpdate(): Promise<void> {}
|
||||
|
||||
async reply(messageOptions: MessageOptions): Promise<Message> {
|
||||
return this.tester.createMessage(messageOptions)
|
||||
}
|
||||
|
||||
async followUp(messageOptions: MessageOptions): Promise<Message> {
|
||||
return this.tester.createMessage(messageOptions)
|
||||
}
|
||||
}
|
||||
|
||||
class TestButtonInteraction
|
||||
@@ -203,6 +221,12 @@ class TestButtonInteraction
|
||||
implements ButtonInteraction
|
||||
{
|
||||
readonly type = "button"
|
||||
readonly event: ButtonClickEvent
|
||||
|
||||
constructor(customId: string, message: TestMessage, tester: ReacordTester) {
|
||||
super(customId, message, tester)
|
||||
this.event = new TestButtonClickEvent(tester)
|
||||
}
|
||||
}
|
||||
|
||||
class TestSelectInteraction
|
||||
@@ -210,13 +234,41 @@ class TestSelectInteraction
|
||||
implements SelectInteraction
|
||||
{
|
||||
readonly type = "select"
|
||||
readonly event: SelectChangeEvent
|
||||
|
||||
constructor(
|
||||
customId: string,
|
||||
message: TestMessage,
|
||||
readonly values: string[],
|
||||
tester: ReacordTester,
|
||||
) {
|
||||
super(customId, message)
|
||||
super(customId, message, tester)
|
||||
this.event = new TestSelectChangeEvent(values, tester)
|
||||
}
|
||||
}
|
||||
|
||||
class TestComponentEvent {
|
||||
constructor(private tester: ReacordTester) {}
|
||||
|
||||
reply(content?: ReactNode): ReacordInstance {
|
||||
return this.tester.reply()
|
||||
}
|
||||
|
||||
ephemeralReply(content?: ReactNode): ReacordInstance {
|
||||
return this.tester.ephemeralReply()
|
||||
}
|
||||
}
|
||||
|
||||
class TestButtonClickEvent
|
||||
extends TestComponentEvent
|
||||
implements ButtonClickEvent {}
|
||||
|
||||
class TestSelectChangeEvent
|
||||
extends TestComponentEvent
|
||||
implements SelectChangeEvent
|
||||
{
|
||||
constructor(readonly values: string[], tester: ReacordTester) {
|
||||
super(tester)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ReactNode } from "react"
|
||||
import type { ComponentInteraction } from "../internal/interaction"
|
||||
import { reconciler } from "../internal/reconciler.js"
|
||||
import type { Renderer } from "../internal/renderers/renderer"
|
||||
import type { ReacordInstance } from "./instance"
|
||||
|
||||
export type ReacordConfig = {
|
||||
/**
|
||||
@@ -11,12 +12,6 @@ export type ReacordConfig = {
|
||||
maxInstances?: number
|
||||
}
|
||||
|
||||
export type ReacordInstance = {
|
||||
render: (content: ReactNode) => void
|
||||
deactivate: () => void
|
||||
destroy: () => void
|
||||
}
|
||||
|
||||
export type ComponentInteractionListener = (
|
||||
interaction: ComponentInteraction,
|
||||
) => void
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
import type { ComponentEvent } from "../core/component-event"
|
||||
import type { ButtonClickEvent, SelectChangeEvent } from "../main"
|
||||
import type { Message, MessageOptions } from "./message"
|
||||
|
||||
export type Interaction = CommandInteraction | ComponentInteraction
|
||||
export type ComponentInteraction = ButtonInteraction | SelectInteraction
|
||||
|
||||
export type CommandInteraction = {
|
||||
type: "command"
|
||||
export type CommandInteraction = BaseInteraction<"command">
|
||||
|
||||
export type ButtonInteraction = BaseComponentInteraction<
|
||||
"button",
|
||||
ButtonClickEvent
|
||||
>
|
||||
|
||||
export type SelectInteraction = BaseComponentInteraction<
|
||||
"select",
|
||||
SelectChangeEvent
|
||||
>
|
||||
|
||||
export type BaseInteraction<Type extends string> = {
|
||||
type: Type
|
||||
id: string
|
||||
channelId: string
|
||||
reply(messageOptions: MessageOptions): Promise<Message>
|
||||
followUp(messageOptions: MessageOptions): Promise<Message>
|
||||
}
|
||||
|
||||
export type ComponentInteraction = ButtonInteraction | SelectInteraction
|
||||
|
||||
export type ButtonInteraction = {
|
||||
type: "button"
|
||||
id: string
|
||||
channelId: string
|
||||
export type BaseComponentInteraction<
|
||||
Type extends string,
|
||||
Event extends ComponentEvent,
|
||||
> = BaseInteraction<Type> & {
|
||||
event: Event
|
||||
customId: string
|
||||
update(options: MessageOptions): Promise<void>
|
||||
deferUpdate(): Promise<void>
|
||||
}
|
||||
|
||||
export type SelectInteraction = {
|
||||
type: "select"
|
||||
id: string
|
||||
channelId: string
|
||||
customId: string
|
||||
values: string[]
|
||||
update(options: MessageOptions): Promise<void>
|
||||
deferUpdate(): Promise<void>
|
||||
}
|
||||
|
||||
24
library/internal/limited-collection.ts
Normal file
24
library/internal/limited-collection.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export class LimitedCollection<T> {
|
||||
private items: T[] = []
|
||||
|
||||
constructor(private readonly size: number) {}
|
||||
|
||||
add(item: T) {
|
||||
if (this.items.length >= this.size) {
|
||||
this.items.shift()
|
||||
}
|
||||
this.items.push(item)
|
||||
}
|
||||
|
||||
has(item: T) {
|
||||
return this.items.includes(item)
|
||||
}
|
||||
|
||||
values(): readonly T[] {
|
||||
return this.items
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.items[Symbol.iterator]()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CommandInteraction } from "../interaction"
|
||||
import type { Interaction } from "../interaction"
|
||||
import type { Message, MessageOptions } from "../message"
|
||||
import { Renderer } from "./renderer"
|
||||
|
||||
@@ -6,8 +6,8 @@ import { Renderer } from "./renderer"
|
||||
// so we know whether to call reply() or followUp()
|
||||
const repliedInteractionIds = new Set<string>()
|
||||
|
||||
export class CommandReplyRenderer extends Renderer {
|
||||
constructor(private interaction: CommandInteraction) {
|
||||
export class InteractionReplyRenderer extends Renderer {
|
||||
constructor(private interaction: Interaction) {
|
||||
super()
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Container } from "../container.js"
|
||||
import type { ComponentInteraction } from "../interaction"
|
||||
import type { Message, MessageOptions } from "../message"
|
||||
import type { Node } from "../node.js"
|
||||
import { Timeout } from "../timeout"
|
||||
|
||||
type UpdatePayload =
|
||||
| { action: "update" | "deactivate"; options: MessageOptions }
|
||||
| { action: "deferUpdate"; interaction: ComponentInteraction }
|
||||
| { action: "destroy" }
|
||||
|
||||
export abstract class Renderer {
|
||||
@@ -21,10 +21,6 @@ export abstract class Renderer {
|
||||
.pipe(concatMap((payload) => this.updateMessage(payload)))
|
||||
.subscribe({ error: console.error })
|
||||
|
||||
private deferUpdateTimeout = new Timeout(500, () => {
|
||||
this.componentInteraction?.deferUpdate().catch(console.error)
|
||||
})
|
||||
|
||||
render() {
|
||||
if (!this.active) {
|
||||
console.warn("Attempted to update a deactivated message")
|
||||
@@ -52,7 +48,11 @@ export abstract class Renderer {
|
||||
|
||||
handleComponentInteraction(interaction: ComponentInteraction) {
|
||||
this.componentInteraction = interaction
|
||||
this.deferUpdateTimeout.run()
|
||||
|
||||
setTimeout(() => {
|
||||
this.updates.next({ action: "deferUpdate", interaction })
|
||||
}, 500)
|
||||
|
||||
for (const node of this.nodes) {
|
||||
if (node.handleComponentInteraction(interaction)) {
|
||||
return true
|
||||
@@ -87,10 +87,14 @@ export abstract class Renderer {
|
||||
return
|
||||
}
|
||||
|
||||
if (payload.action === "deferUpdate") {
|
||||
await payload.interaction.deferUpdate()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.componentInteraction) {
|
||||
const promise = this.componentInteraction.update(payload.options)
|
||||
this.componentInteraction = undefined
|
||||
this.deferUpdateTimeout.cancel()
|
||||
await promise
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./core/component-event"
|
||||
export * from "./core/components/action-row"
|
||||
export * from "./core/components/button"
|
||||
export * from "./core/components/embed"
|
||||
@@ -10,6 +11,7 @@ export * from "./core/components/embed-title"
|
||||
export * from "./core/components/link"
|
||||
export * from "./core/components/option"
|
||||
export * from "./core/components/select"
|
||||
export * from "./core/instance"
|
||||
export * from "./core/reacord"
|
||||
export * from "./core/reacord-discord-js"
|
||||
export * from "./core/reacord-tester"
|
||||
|
||||
Reference in New Issue
Block a user